tamperward 2.23.5 → 2.23.7

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 CHANGED
@@ -206,7 +206,7 @@ function forwardedEnv(env) {
206
206
  return out3;
207
207
  }
208
208
  function exchange(paths, req2, timeoutMs) {
209
- return new Promise((resolve15) => {
209
+ return new Promise((resolve16) => {
210
210
  let done = false;
211
211
  let handedOff = false;
212
212
  let accepted = false;
@@ -217,7 +217,7 @@ function exchange(paths, req2, timeoutMs) {
217
217
  done = true;
218
218
  clearTimeout(timer);
219
219
  sock.destroy();
220
- resolve15(value);
220
+ resolve16(value);
221
221
  };
222
222
  const sock = createConnection(paths.socket);
223
223
  const timer = setTimeout(() => finish(handedOff ? acceptedFailure("timeout") : null), timeoutMs);
@@ -603,12 +603,12 @@ function irregularName(st) {
603
603
  function meta(st) {
604
604
  return { mode: st.mode, size: st.size, mtimeMs: st.mtimeMs };
605
605
  }
606
- function readRegular(abs2, st) {
606
+ function readRegular(abs, st) {
607
607
  let fd;
608
608
  try {
609
- fd = openSync(abs2, O_READ);
609
+ fd = openSync(abs, O_READ);
610
610
  } catch (e) {
611
- if (errCode(e) === "ELOOP") return inspectOnce(abs2);
611
+ if (errCode(e) === "ELOOP") return inspectOnce(abs);
612
612
  return { kind: "unreadable", content: null, ...meta(st), detail: errText(e) };
613
613
  }
614
614
  try {
@@ -629,10 +629,10 @@ function readRegular(abs2, st) {
629
629
  closeSync(fd);
630
630
  }
631
631
  }
632
- function classify(abs2, st) {
632
+ function classify(abs, st) {
633
633
  if (st.isSymbolicLink()) {
634
634
  try {
635
- const target = readlinkSync(abs2);
635
+ const target = readlinkSync(abs);
636
636
  return { kind: "symlink", content: Buffer.from(target, "utf8"), ...meta(st), detail: target };
637
637
  } catch (e) {
638
638
  return { kind: "unreadable", content: null, ...meta(st), detail: errText(e) };
@@ -641,38 +641,38 @@ function classify(abs2, st) {
641
641
  if (st.isDirectory()) return { kind: "directory", content: null, ...meta(st), detail: "directory" };
642
642
  if (!st.isFile()) return { kind: "irregular", content: null, ...meta(st), detail: irregularName(st) };
643
643
  if (st.size > READ_CAP) return { kind: "oversize", content: null, ...meta(st), detail: `${st.size} bytes` };
644
- return readRegular(abs2, st);
644
+ return readRegular(abs, st);
645
645
  }
646
- function inspectOnce(abs2) {
646
+ function inspectOnce(abs) {
647
647
  let st;
648
648
  try {
649
- st = lstatSync2(abs2);
649
+ st = lstatSync2(abs);
650
650
  } catch (e) {
651
651
  const code2 = errCode(e);
652
652
  if (code2 === "ENOENT" || code2 === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
653
653
  return { kind: "unreadable", content: null, ...NO_META, detail: errText(e) };
654
654
  }
655
- if (st.isSymbolicLink() || !st.isFile()) return classify(abs2, st);
655
+ if (st.isSymbolicLink() || !st.isFile()) return classify(abs, st);
656
656
  return { kind: "unreadable", content: null, ...meta(st), detail: "the path changed shape while it was being read" };
657
657
  }
658
- function inspectPath(abs2) {
658
+ function inspectPath(abs) {
659
659
  let st;
660
660
  try {
661
- st = lstatSync2(abs2);
661
+ st = lstatSync2(abs);
662
662
  } catch (e) {
663
663
  const code2 = errCode(e);
664
664
  if (code2 === "ENOENT" || code2 === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
665
665
  return { kind: "unreadable", content: null, ...NO_META, detail: errText(e) };
666
666
  }
667
- return classify(abs2, st);
667
+ return classify(abs, st);
668
668
  }
669
669
  function inspectRel(cwd, rel) {
670
670
  return inspectPath(join3(cwd, rel));
671
671
  }
672
- function inspectResolved(abs2) {
672
+ function inspectResolved(abs) {
673
673
  let real;
674
674
  try {
675
- real = realpathSync2(abs2);
675
+ real = realpathSync2(abs);
676
676
  } catch (e) {
677
677
  const code2 = errCode(e);
678
678
  if (code2 === "ENOENT" || code2 === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
@@ -1021,11 +1021,55 @@ var init_trusted = __esm({
1021
1021
  }
1022
1022
  });
1023
1023
 
1024
- // src/git/build.ts
1024
+ // src/repo-context.ts
1025
1025
  import { execFileSync as execFileSync2 } from "node:child_process";
1026
+ import { existsSync as existsSync2 } from "node:fs";
1027
+ import { isAbsolute as isAbsolute3, resolve } from "node:path";
1028
+ function rev(cwd, flag) {
1029
+ try {
1030
+ const out3 = execFileSync2("git", ["rev-parse", flag], {
1031
+ cwd,
1032
+ encoding: "utf8",
1033
+ stdio: ["ignore", "pipe", "pipe"]
1034
+ }).trim();
1035
+ return out3 ? isAbsolute3(out3) ? out3 : resolve(cwd, out3) : null;
1036
+ } catch {
1037
+ return null;
1038
+ }
1039
+ }
1040
+ function repoContext(cwd) {
1041
+ const key2 = resolve(cwd);
1042
+ const hit = cache.get(key2);
1043
+ if (hit && existsSync2(hit.gitDir) && existsSync2(hit.root)) return hit;
1044
+ cache.delete(key2);
1045
+ const root = rev(key2, "--show-toplevel");
1046
+ const gitDir3 = root ? rev(key2, "--absolute-git-dir") : null;
1047
+ if (!root || !gitDir3) return null;
1048
+ const ctx = { root, gitDir: gitDir3 };
1049
+ if (cache.size >= CACHE_MAX) {
1050
+ const oldest = cache.keys().next().value;
1051
+ if (oldest !== void 0) cache.delete(oldest);
1052
+ }
1053
+ cache.set(key2, ctx);
1054
+ return ctx;
1055
+ }
1056
+ function repoRoot(cwd) {
1057
+ return repoContext(cwd)?.root ?? cwd;
1058
+ }
1059
+ var cache, CACHE_MAX;
1060
+ var init_repo_context = __esm({
1061
+ "src/repo-context.ts"() {
1062
+ "use strict";
1063
+ cache = /* @__PURE__ */ new Map();
1064
+ CACHE_MAX = 256;
1065
+ }
1066
+ });
1067
+
1068
+ // src/git/build.ts
1069
+ import { execFileSync as execFileSync3 } from "node:child_process";
1026
1070
  function git(args, cwd) {
1027
1071
  try {
1028
- return execFileSync2("git", args, {
1072
+ return execFileSync3("git", args, {
1029
1073
  cwd,
1030
1074
  encoding: "utf8",
1031
1075
  // 256 MiB, matching the hook adapter. Every diff below runs with `--text`, so a
@@ -1047,15 +1091,15 @@ function git(args, cwd) {
1047
1091
  throw new Error(detail ? `git ${args[0]}: ${detail}` : failure.message || `git ${args[0]} failed`);
1048
1092
  }
1049
1093
  }
1050
- function blobAt(rev, path, cwd) {
1094
+ function blobAt(rev2, path, cwd) {
1051
1095
  try {
1052
- return git(["show", `${rev}:${path}`], cwd);
1096
+ return git(["show", `${rev2}:${path}`], cwd);
1053
1097
  } catch {
1054
1098
  return null;
1055
1099
  }
1056
1100
  }
1057
1101
  function fromDisk(path, cwd) {
1058
- return textOf2(inspectRel(cwd ?? process.cwd(), path));
1102
+ return textOf2(inspectRel(repoRoot(cwd ?? process.cwd()), path));
1059
1103
  }
1060
1104
  function enrich(c, beforeReader, afterReader) {
1061
1105
  if (c.kind !== "file") return c;
@@ -1063,9 +1107,9 @@ function enrich(c, beforeReader, afterReader) {
1063
1107
  const after = c.op !== "delete" ? afterReader(c.path) : null;
1064
1108
  return { ...c, before, after };
1065
1109
  }
1066
- function assertRev(rev) {
1067
- if (rev.startsWith("-")) throw new Error(`refusing a revision that git would read as an option: ${rev}`);
1068
- return rev;
1110
+ function assertRev(rev2) {
1111
+ if (rev2.startsWith("-")) throw new Error(`refusing a revision that git would read as an option: ${rev2}`);
1112
+ return rev2;
1069
1113
  }
1070
1114
  function mergeBaseOf(base, head, opts = {}) {
1071
1115
  try {
@@ -1074,8 +1118,8 @@ function mergeBaseOf(base, head, opts = {}) {
1074
1118
  return base;
1075
1119
  }
1076
1120
  }
1077
- function fileAt(rev, path, opts = {}) {
1078
- return blobAt(rev, path, opts.cwd);
1121
+ function fileAt(rev2, path, opts = {}) {
1122
+ return blobAt(rev2, path, opts.cwd);
1079
1123
  }
1080
1124
  function fileOnDisk(path, opts = {}) {
1081
1125
  return fromDisk(path, opts.cwd);
@@ -1102,10 +1146,10 @@ function headSha(cwd) {
1102
1146
  return null;
1103
1147
  }
1104
1148
  }
1105
- function diffSince(rev, opts = {}) {
1106
- const raw = git([...DIFF_ARGS, rev], opts.cwd);
1149
+ function diffSince(rev2, opts = {}) {
1150
+ const raw = git([...DIFF_ARGS, rev2], opts.cwd);
1107
1151
  return parseDiff(raw).map(
1108
- (c) => enrich(c, (p) => blobAt(rev, p, opts.cwd), (p) => fromDisk(p, opts.cwd))
1152
+ (c) => enrich(c, (p) => blobAt(rev2, p, opts.cwd), (p) => fromDisk(p, opts.cwd))
1109
1153
  );
1110
1154
  }
1111
1155
  function diffRange(base, head, opts = {}) {
@@ -1195,6 +1239,7 @@ var init_build = __esm({
1195
1239
  init_synth();
1196
1240
  init_trusted();
1197
1241
  init_narrow();
1242
+ init_repo_context();
1198
1243
  DIFF_ARGS = ["diff", "--no-color", "--text", "-M"];
1199
1244
  SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".hg", ".svn"]);
1200
1245
  IGNORED_LS = ["ls-files", "--others", "--ignored", "--exclude-standard", "-z"];
@@ -1202,22 +1247,19 @@ var init_build = __esm({
1202
1247
  });
1203
1248
 
1204
1249
  // src/adapters/claude/changes.ts
1205
- import { execFileSync as execFileSync3 } from "node:child_process";
1250
+ import { execFileSync as execFileSync4 } from "node:child_process";
1206
1251
  import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
1207
1252
  import { tmpdir as tmpdir2 } from "node:os";
1208
- import { isAbsolute as isAbsolute3, join as join5, relative, resolve } from "node:path";
1253
+ import { isAbsolute as isAbsolute4, join as join5, relative, resolve as resolve2 } from "node:path";
1209
1254
  function asStr(v) {
1210
1255
  return typeof v === "string" ? v : "";
1211
1256
  }
1212
1257
  function readDisk(path) {
1213
1258
  return textOf2(inspectResolved(path));
1214
1259
  }
1215
- function abs(path, cwd) {
1216
- return resolve(cwd, path);
1217
- }
1218
1260
  function relForDisplay(path, cwd) {
1219
1261
  const rel = relative(cwd, path);
1220
- return rel && !rel.startsWith("..") && !isAbsolute3(rel) ? rel : path;
1262
+ return rel && !rel.startsWith("..") && !isAbsolute4(rel) ? rel : path;
1221
1263
  }
1222
1264
  function applyEdit(content, oldStr, newStr) {
1223
1265
  if (content === null) return newStr;
@@ -1238,7 +1280,7 @@ function synthFileChange(displayPath, before, after) {
1238
1280
  writeFileSync(a, before ?? "");
1239
1281
  writeFileSync(b, after ?? "");
1240
1282
  try {
1241
- raw = execFileSync3("git", ["diff", "--no-index", "--no-color", "--text", a, b], {
1283
+ raw = execFileSync4("git", ["diff", "--no-index", "--no-color", "--text", a, b], {
1242
1284
  encoding: "utf8",
1243
1285
  maxBuffer: 256 * 1024 * 1024
1244
1286
  });
@@ -1256,8 +1298,9 @@ function synthFileChange(displayPath, before, after) {
1256
1298
  const hunks = first && first.kind === "file" ? first.hunks : [];
1257
1299
  return [{ kind: "file", path: displayPath, oldPath: null, op, before, after, binary: false, hunks }];
1258
1300
  }
1259
- function changesFromClaudeHook(input, cwd) {
1301
+ function changesFromClaudeHook(input, cwd, base = cwd) {
1260
1302
  const ti = input.tool_input ?? {};
1303
+ const abs = (path) => resolve2(base, path);
1261
1304
  switch (input.tool_name) {
1262
1305
  case "Bash": {
1263
1306
  const raw = asStr(ti.command);
@@ -1266,33 +1309,33 @@ function changesFromClaudeHook(input, cwd) {
1266
1309
  case "Write": {
1267
1310
  const fp = asStr(ti.file_path);
1268
1311
  if (!fp) return [];
1269
- const before = readDisk(abs(fp, cwd));
1270
- return synthFileChange(relForDisplay(abs(fp, cwd), cwd), before, asStr(ti.content));
1312
+ const before = readDisk(abs(fp));
1313
+ return synthFileChange(relForDisplay(abs(fp), cwd), before, asStr(ti.content));
1271
1314
  }
1272
1315
  case "Edit": {
1273
1316
  const fp = asStr(ti.file_path);
1274
1317
  if (!fp) return [];
1275
- const before = readDisk(abs(fp, cwd));
1318
+ const before = readDisk(abs(fp));
1276
1319
  const after = applyEdit(before, asStr(ti.old_string), asStr(ti.new_string));
1277
- return synthFileChange(relForDisplay(abs(fp, cwd), cwd), before, after);
1320
+ return synthFileChange(relForDisplay(abs(fp), cwd), before, after);
1278
1321
  }
1279
1322
  case "MultiEdit": {
1280
1323
  const fp = asStr(ti.file_path);
1281
1324
  if (!fp) return [];
1282
- const before = readDisk(abs(fp, cwd));
1325
+ const before = readDisk(abs(fp));
1283
1326
  let after = before;
1284
1327
  const edits = Array.isArray(ti.edits) ? ti.edits : [];
1285
1328
  for (const raw of edits) {
1286
1329
  const ed = isRecord(raw) ? raw : {};
1287
1330
  after = applyEdit(after, asStr(ed.old_string), asStr(ed.new_string));
1288
1331
  }
1289
- return synthFileChange(relForDisplay(abs(fp, cwd), cwd), before, after);
1332
+ return synthFileChange(relForDisplay(abs(fp), cwd), before, after);
1290
1333
  }
1291
1334
  case "NotebookEdit": {
1292
1335
  const fp = asStr(ti.notebook_path);
1293
1336
  const src = asStr(ti.new_source);
1294
1337
  if (!fp || !src) return [];
1295
- return synthFileChange(relForDisplay(abs(fp, cwd), cwd), "", src);
1338
+ return synthFileChange(relForDisplay(abs(fp), cwd), "", src);
1296
1339
  }
1297
1340
  default:
1298
1341
  return [];
@@ -2506,18 +2549,18 @@ var init_test_skip = __esm({
2506
2549
  });
2507
2550
 
2508
2551
  // src/detectors/repo.ts
2509
- import { execFileSync as execFileSync4 } from "node:child_process";
2552
+ import { execFileSync as execFileSync5 } from "node:child_process";
2510
2553
  import { readFileSync as readFileSync3 } from "node:fs";
2511
2554
  import { join as join6 } from "node:path";
2512
2555
  function trackedFiles(ctx) {
2513
2556
  if (!ctx) return null;
2514
2557
  if (ctx.trackedFiles) return ctx.trackedFiles;
2515
2558
  if (!ctx.cwd) return null;
2516
- const hit = cache.get(ctx.cwd);
2559
+ const hit = cache2.get(ctx.cwd);
2517
2560
  if (hit !== void 0) return hit;
2518
2561
  let files = null;
2519
2562
  try {
2520
- files = execFileSync4("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
2563
+ files = execFileSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
2521
2564
  cwd: ctx.cwd,
2522
2565
  encoding: "utf8",
2523
2566
  maxBuffer: 64 * 1024 * 1024,
@@ -2526,7 +2569,7 @@ function trackedFiles(ctx) {
2526
2569
  } catch {
2527
2570
  files = null;
2528
2571
  }
2529
- cache.set(ctx.cwd, files);
2572
+ cache2.set(ctx.cwd, files);
2530
2573
  return files;
2531
2574
  }
2532
2575
  function trackedContent(path, ctx) {
@@ -2551,7 +2594,7 @@ function branchRefs(ctx) {
2551
2594
  if (hit !== void 0) return hit;
2552
2595
  let refs = null;
2553
2596
  try {
2554
- const out3 = execFileSync4("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads", "refs/remotes"], {
2597
+ const out3 = execFileSync5("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads", "refs/remotes"], {
2555
2598
  cwd: ctx.cwd,
2556
2599
  encoding: "utf8",
2557
2600
  stdio: ["ignore", "pipe", "pipe"]
@@ -2570,11 +2613,11 @@ function branchExists(name, ctx) {
2570
2613
  for (const r of refs) if (r.endsWith("/" + name)) return true;
2571
2614
  return false;
2572
2615
  }
2573
- function revIsHead(rev, ctx) {
2574
- if (!ctx?.cwd || rev.startsWith("-")) return null;
2616
+ function revIsHead(rev2, ctx) {
2617
+ if (!ctx?.cwd || rev2.startsWith("-")) return null;
2575
2618
  const parse = (r) => {
2576
2619
  try {
2577
- return execFileSync4("git", ["rev-parse", "--verify", "--quiet", "--end-of-options", `${r}^{commit}`], {
2620
+ return execFileSync5("git", ["rev-parse", "--verify", "--quiet", "--end-of-options", `${r}^{commit}`], {
2578
2621
  cwd: ctx.cwd,
2579
2622
  encoding: "utf8",
2580
2623
  stdio: ["ignore", "pipe", "pipe"]
@@ -2584,7 +2627,7 @@ function revIsHead(rev, ctx) {
2584
2627
  }
2585
2628
  };
2586
2629
  const head = parse("HEAD");
2587
- const target = parse(rev);
2630
+ const target = parse(rev2);
2588
2631
  if (!head || !target) return null;
2589
2632
  return head === target;
2590
2633
  }
@@ -2594,7 +2637,7 @@ function defaultBranch(ctx) {
2594
2637
  if (hit !== void 0) return hit;
2595
2638
  let name = null;
2596
2639
  try {
2597
- const ref = execFileSync4("git", ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"], {
2640
+ const ref = execFileSync5("git", ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"], {
2598
2641
  cwd: ctx.cwd,
2599
2642
  encoding: "utf8",
2600
2643
  stdio: ["ignore", "pipe", "pipe"]
@@ -2619,12 +2662,12 @@ function containsProtected(token, policy, category, ctx, except) {
2619
2662
  const base = dir.split("/").pop() ?? "";
2620
2663
  return category === "tests" && /^(?:test|tests|__tests__|spec|specs)$/i.test(base);
2621
2664
  }
2622
- var cache, contentCache, branchCache, refsCache, dirOf;
2665
+ var cache2, contentCache, branchCache, refsCache, dirOf;
2623
2666
  var init_repo = __esm({
2624
2667
  "src/detectors/repo.ts"() {
2625
2668
  "use strict";
2626
2669
  init_policy();
2627
- cache = /* @__PURE__ */ new Map();
2670
+ cache2 = /* @__PURE__ */ new Map();
2628
2671
  contentCache = /* @__PURE__ */ new Map();
2629
2672
  branchCache = /* @__PURE__ */ new Map();
2630
2673
  refsCache = /* @__PURE__ */ new Map();
@@ -2645,8 +2688,8 @@ function numericOf(e) {
2645
2688
  }
2646
2689
  return void 0;
2647
2690
  }
2648
- function metricsOf(expr, resolve15) {
2649
- const obj = resolve15(expr);
2691
+ function metricsOf(expr, resolve16) {
2692
+ const obj = resolve16(expr);
2650
2693
  if (!ts.isObjectLiteralExpression(obj)) return void 0;
2651
2694
  const out3 = { values: {}, opaque: false };
2652
2695
  let any = false;
@@ -2660,7 +2703,7 @@ function metricsOf(expr, resolve15) {
2660
2703
  const k = keyName(p.name);
2661
2704
  if (k === null || !isMetric(k)) continue;
2662
2705
  any = true;
2663
- const n = numericOf(resolve15(p.initializer));
2706
+ const n = numericOf(resolve16(p.initializer));
2664
2707
  if (n === void 0) out3.opaque = true;
2665
2708
  else out3.values[k] = n;
2666
2709
  }
@@ -2706,17 +2749,17 @@ function parseThresholds(src) {
2706
2749
  const res = { paths: /* @__PURE__ */ new Map(), present: false, opaque: false };
2707
2750
  try {
2708
2751
  const sf = sourceOf(src);
2709
- const resolve15 = resolverFor(sf);
2752
+ const resolve16 = resolverFor(sf);
2710
2753
  const visit = (node) => {
2711
2754
  if (ts.isPropertyAssignment(node)) {
2712
2755
  const key2 = keyName(node.name);
2713
2756
  if (key2 === "coverageThreshold" || key2 === "thresholds" && underKey(node, "coverage")) {
2714
2757
  res.present = true;
2715
- const init = resolve15(node.initializer);
2758
+ const init = resolve16(node.initializer);
2716
2759
  if (!ts.isObjectLiteralExpression(init)) {
2717
2760
  res.opaque = true;
2718
2761
  } else {
2719
- const flat = metricsOf(init, resolve15);
2762
+ const flat = metricsOf(init, resolve16);
2720
2763
  if (flat) res.global = merge(res.global, flat);
2721
2764
  for (const p of init.properties) {
2722
2765
  if (ts.isSpreadAssignment(p)) {
@@ -2730,7 +2773,7 @@ function parseThresholds(src) {
2730
2773
  res.global = merge(res.global, { values: { branches: 100, functions: 100, lines: 100, statements: 100 }, opaque: false });
2731
2774
  continue;
2732
2775
  }
2733
- const m = metricsOf(p.initializer, resolve15);
2776
+ const m = metricsOf(p.initializer, resolve16);
2734
2777
  if (!m) continue;
2735
2778
  if (k === "global") res.global = merge(res.global, m);
2736
2779
  else res.paths.set(norm(k), m);
@@ -2763,18 +2806,18 @@ function parseLists(src) {
2763
2806
  const res = { collectFrom: null, collectKey: "collectCoverageFrom", ignore: [], opaqueCollect: false, opaqueIgnore: false };
2764
2807
  try {
2765
2808
  const sf = sourceOf(src);
2766
- const resolve15 = resolverFor(sf);
2809
+ const resolve16 = resolverFor(sf);
2767
2810
  const visit = (node) => {
2768
2811
  if (ts.isPropertyAssignment(node)) {
2769
2812
  const k = keyName(node.name);
2770
2813
  const vitestCoverage = ownerKey(node) === "coverage";
2771
2814
  if (k === "collectCoverageFrom" || k === "include" && vitestCoverage) {
2772
- const { items, opaque } = literals(resolve15(node.initializer));
2815
+ const { items, opaque } = literals(resolve16(node.initializer));
2773
2816
  res.collectFrom = items;
2774
2817
  res.collectKey = k === "include" ? "coverage.include" : k;
2775
2818
  if (opaque) res.opaqueCollect = true;
2776
2819
  } else if (k === "coveragePathIgnorePatterns" || k === "exclude" && vitestCoverage) {
2777
- const { items, opaque } = literals(resolve15(node.initializer));
2820
+ const { items, opaque } = literals(resolve16(node.initializer));
2778
2821
  res.ignore.push(...items);
2779
2822
  if (opaque) res.opaqueIgnore = true;
2780
2823
  }
@@ -4047,8 +4090,8 @@ var init_policy_diff = __esm({
4047
4090
  });
4048
4091
 
4049
4092
  // src/detectors/hook-wiring.ts
4050
- import { existsSync as existsSync2 } from "node:fs";
4051
- import { isAbsolute as isAbsolute4, resolve as resolve2 } from "node:path";
4093
+ import { existsSync as existsSync3 } from "node:fs";
4094
+ import { isAbsolute as isAbsolute5, resolve as resolve3 } from "node:path";
4052
4095
  function stripComment(line) {
4053
4096
  let single = false;
4054
4097
  let double = false;
@@ -5066,7 +5109,7 @@ function expandShellPath(t, env = process.env) {
5066
5109
  }
5067
5110
  function hookTests(policy, ctx) {
5068
5111
  const cwd = ctx?.cwd;
5069
- const diskCwd = cwd !== void 0 && existsSync2(cwd) ? cwd : void 0;
5112
+ const diskCwd = cwd !== void 0 && existsSync3(cwd) ? cwd : void 0;
5070
5113
  const onDisk = diskCwd !== void 0;
5071
5114
  const root = diskCwd !== void 0 ? canonicalPath(diskCwd) : cwd;
5072
5115
  const memo = /* @__PURE__ */ new Map();
@@ -5074,10 +5117,10 @@ function hookTests(policy, ctx) {
5074
5117
  const hit = memo.get(t);
5075
5118
  if (hit !== void 0) return hit;
5076
5119
  let p = expandShellPath(t.replace(/^["']|["']$/g, ""));
5077
- const pathy = isAbsolute4(p) || p.includes("/") && !/^[-:]/.test(p);
5078
- const abs2 = isAbsolute4(p) ? p : diskCwd !== void 0 ? resolve2(diskCwd, p) : null;
5079
- if (pathy && abs2 !== null) {
5080
- p = canonicalPath(abs2);
5120
+ const pathy = isAbsolute5(p) || p.includes("/") && !/^[-:]/.test(p);
5121
+ const abs = isAbsolute5(p) ? p : diskCwd !== void 0 ? resolve3(diskCwd, p) : null;
5122
+ if (pathy && abs !== null) {
5123
+ p = canonicalPath(abs);
5081
5124
  if (root !== void 0) {
5082
5125
  if (p === root) p = ".";
5083
5126
  else if (p.startsWith(root + "/")) p = p.slice(root.length + 1);
@@ -5350,8 +5393,8 @@ var init_hook_wiring = __esm({
5350
5393
  });
5351
5394
 
5352
5395
  // src/detectors/hook-tampering.ts
5353
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
5354
- import { isAbsolute as isAbsolute5, join as join7, relative as relative2 } from "node:path";
5396
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
5397
+ import { isAbsolute as isAbsolute6, join as join7, relative as relative2 } from "node:path";
5355
5398
  function parseSettings(src) {
5356
5399
  try {
5357
5400
  const v = JSON.parse(src);
@@ -5723,7 +5766,7 @@ function lefthookBase(c, changes, ctx) {
5723
5766
  for (const n of names) {
5724
5767
  try {
5725
5768
  const p = join7(ctx.cwd, n);
5726
- if (existsSync3(p)) return parseDoc(readFileSync4(p, "utf8"));
5769
+ if (existsSync4(p)) return parseDoc(readFileSync4(p, "utf8"));
5727
5770
  } catch {
5728
5771
  }
5729
5772
  }
@@ -5793,9 +5836,9 @@ function updateIndexHiding(toks, policy) {
5793
5836
  return null;
5794
5837
  }
5795
5838
  function repoRelative(path, ctx) {
5796
- if (!ctx?.cwd || !isAbsolute5(path)) return path;
5839
+ if (!ctx?.cwd || !isAbsolute6(path)) return path;
5797
5840
  const rel = relative2(ctx.cwd, path);
5798
- return rel && !rel.startsWith("..") && !isAbsolute5(rel) ? rel : path;
5841
+ return rel && !rel.startsWith("..") && !isAbsolute6(rel) ? rel : path;
5799
5842
  }
5800
5843
  function huskyRuntimeWrite(path, content, ctx) {
5801
5844
  const m = path.match(/(?:^|\/)\.husky\/_\/([^/]+)$/);
@@ -6133,7 +6176,7 @@ They WILL FAIL in v10.0.0
6133
6176
  if (why) {
6134
6177
  const target = shellHookTarget(seg, toks, policy, ctx) ?? shellHookTarget("", fed, policy, ctx);
6135
6178
  const file = target ? repoRelative(target, ctx) : void 0;
6136
- const outside = file !== void 0 && (isAbsolute5(file) || /^[A-Za-z]:\//.test(file)) && isClaudeSettings(file) ? ` \u2014 ${file} is a Claude Code settings file the runtime reads hooks from` : "";
6179
+ const outside = file !== void 0 && (isAbsolute6(file) || /^[A-Za-z]:\//.test(file)) && isClaudeSettings(file) ? ` \u2014 ${file} is a Claude Code settings file the runtime reads hooks from` : "";
6137
6180
  out3.push(
6138
6181
  makeFinding(RULE7, policy, {
6139
6182
  ...file !== void 0 ? { file } : {},
@@ -6154,6 +6197,30 @@ They WILL FAIL in v10.0.0
6154
6197
  });
6155
6198
 
6156
6199
  // src/detectors/suite-config.ts
6200
+ function runnerSkips(path) {
6201
+ const parts = path.split("/");
6202
+ if (/\.rs$/.test(path)) {
6203
+ const i = parts.indexOf("tests");
6204
+ if (i < 0 || parts.slice(0, i).includes("src")) return null;
6205
+ const rest = parts.slice(i + 1);
6206
+ if (rest.length === 1 || rest.length === 2 && rest[1] === "main.rs") return null;
6207
+ return "cargo builds only tests/*.rs and tests/*/main.rs as test targets";
6208
+ }
6209
+ if (/\.go$/.test(path)) {
6210
+ const hit = parts.find((p) => p === "testdata" || p.startsWith("_") || p.startsWith("."));
6211
+ if (hit === void 0) return null;
6212
+ return `go test skips ${hit === "testdata" ? "testdata/" : hit.startsWith("_") ? "_*" : ".*"} paths (${JSON.stringify(hit)})`;
6213
+ }
6214
+ if (/\.py$/.test(path)) {
6215
+ const hit = PYTEST_DEFAULT_IGNORE.find((g) => globs3([g])(path));
6216
+ return hit === void 0 ? null : `pytest's default norecursedirs matches it (${JSON.stringify(hit)})`;
6217
+ }
6218
+ if (/\.[cm]?[jt]sx?$/.test(path)) {
6219
+ const hit = JS_UNWALKED.find((g) => globs3([g])(path));
6220
+ return hit === void 0 ? null : `the runner's default exclude matches it (${JSON.stringify(hit)})`;
6221
+ }
6222
+ return null;
6223
+ }
6157
6224
  function keyName2(name) {
6158
6225
  if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
6159
6226
  return null;
@@ -6494,7 +6561,7 @@ function suiteNarrowings(before, after, path, samples) {
6494
6561
  }
6495
6562
  return out3;
6496
6563
  }
6497
- var JEST_DEFAULT_MATCH, JEST_DEFAULT_IGNORE, VITEST_DEFAULT_INCLUDE, PYTEST_DEFAULT_INCLUDE, PYTEST_DEFAULT_IGNORE, VITEST_DEFAULT_EXCLUDE, CANONICAL_SAMPLES, PYTEST_CANONICAL_SAMPLES, isStr, PYTEST_INI_ORDER, pathGlobs, isWorkspaceFile, empty, stripRootDir, JEST_SELECTION_KEYS, globs3, globList2, regexes, underDir;
6564
+ var JEST_DEFAULT_MATCH, JEST_DEFAULT_IGNORE, VITEST_DEFAULT_INCLUDE, PYTEST_DEFAULT_INCLUDE, PYTEST_DEFAULT_IGNORE, VITEST_DEFAULT_EXCLUDE, JS_UNWALKED, CANONICAL_SAMPLES, PYTEST_CANONICAL_SAMPLES, isStr, PYTEST_INI_ORDER, pathGlobs, isWorkspaceFile, empty, stripRootDir, JEST_SELECTION_KEYS, globs3, globList2, regexes, underDir;
6498
6565
  var init_suite_config = __esm({
6499
6566
  "src/detectors/suite-config.ts"() {
6500
6567
  "use strict";
@@ -6512,6 +6579,7 @@ var init_suite_config = __esm({
6512
6579
  "**/.{idea,git,cache,output,temp}/**",
6513
6580
  "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*"
6514
6581
  ];
6582
+ JS_UNWALKED = [...VITEST_DEFAULT_EXCLUDE, "**/.*/**"];
6515
6583
  CANONICAL_SAMPLES = [
6516
6584
  "src/a.test.ts",
6517
6585
  "src/a.spec.ts",
@@ -6743,6 +6811,9 @@ function leavesGlob(dir, dest, policy, ctx) {
6743
6811
  const to = dest.replace(/^\.\//, "").replace(/\/+$/, "") + "/";
6744
6812
  return files.some((f) => f.startsWith(src) && isProtected(f, policy, "tests") && !isProtected(to + f.slice(src.length), policy, "tests"));
6745
6813
  }
6814
+ function leavesWalk(from, to) {
6815
+ return runnerSkips(from) === null ? runnerSkips(to) : null;
6816
+ }
6746
6817
  function pytestPrecedenceFindings(changes, policy, ctx) {
6747
6818
  const touched = /* @__PURE__ */ new Map();
6748
6819
  for (const c of changes) {
@@ -6859,6 +6930,15 @@ var init_test_deletion = __esm({
6859
6930
  remediation: "Restoring the path. Renaming a spec out of the glob silently removes it from the suite."
6860
6931
  })
6861
6932
  );
6933
+ } else if (c.op === "rename" && c.oldPath && isSpec(c.oldPath) && isTest && leavesWalk(c.oldPath, c.path)) {
6934
+ out3.push(
6935
+ makeFinding(RULE8, policy, {
6936
+ file: c.path,
6937
+ message: `A test file was renamed into a path the runner does not walk (${c.oldPath} \u2192 ${c.path}): ${leavesWalk(c.oldPath, c.path)}.`,
6938
+ evidence: `${c.oldPath} \u2192 ${c.path}`,
6939
+ remediation: "Restoring the path. A spec the runner never opens is removed from the suite as surely as a deleted one."
6940
+ })
6941
+ );
6862
6942
  } else if ((c.op === "modify" || c.op === "rename") && isTest && c.before != null && c.after != null) {
6863
6943
  const before = countTests(c.before, c.path);
6864
6944
  const after = countTests(c.after, c.path);
@@ -6941,8 +7021,8 @@ var init_test_deletion = __esm({
6941
7021
  const gitRestoresOld = cmd === "git" && /^git\s+(?:checkout|restore)\b/.test(seg) && (() => {
6942
7022
  const src = seg.match(/--source[= ]\s*(\S+)/)?.[1];
6943
7023
  const revIdx = toks.findIndex((t, i) => i >= 2 && !t.startsWith("-") && !named.includes(t) && t !== "--");
6944
- const rev = src ?? (revIdx >= 0 ? toks[revIdx] : void 0);
6945
- if (!rev || rev === "HEAD" || rev === "@" || revIsHead(rev, ctx) === true) return false;
7024
+ const rev2 = src ?? (revIdx >= 0 ? toks[revIdx] : void 0);
7025
+ if (!rev2 || rev2 === "HEAD" || rev2 === "@" || revIsHead(rev2, ctx) === true) return false;
6946
7026
  if (src || seg.includes(" -- ")) return true;
6947
7027
  return revIdx >= 0 && toks.some((t, i) => i > revIdx && named.includes(t));
6948
7028
  })();
@@ -6971,6 +7051,9 @@ var init_test_deletion = __esm({
6971
7051
  if (dest && testToks.length > 0 && !isSpec(dest)) {
6972
7052
  why = "mv renames a test file out of the test glob";
6973
7053
  evidence = `${testToks[0]} \u2192 ${dest}`;
7054
+ } else if (dest && testToks.length > 0 && testToks[0] !== dest && leavesWalk(testToks[0], dest)) {
7055
+ why = "mv renames a test file into a path the runner does not walk";
7056
+ evidence = `${testToks[0]} \u2192 ${dest}`;
6974
7057
  } else if (dest && testToks.length === 0 && dirToks[0] !== dest && leavesGlob(dirToks[0] ?? "", dest, policy, ctx)) {
6975
7058
  why = "mv renames a test directory so its specs leave the test glob";
6976
7059
  evidence = `${dirToks[0]} \u2192 ${dest}`;
@@ -7492,7 +7575,9 @@ function key(f) {
7492
7575
  function isSuppressed(c, policy) {
7493
7576
  if (c.kind !== "file") return false;
7494
7577
  if (isPolicyFile(c.path) || c.oldPath != null && isPolicyFile(c.oldPath)) return false;
7495
- return isIgnored(c.path, policy);
7578
+ if (!isIgnored(c.path, policy)) return false;
7579
+ if (c.oldPath == null) return true;
7580
+ return isIgnored(c.oldPath, policy) && !isProtected(c.oldPath, policy);
7496
7581
  }
7497
7582
  function activeChanges(changes, policy) {
7498
7583
  return changes.filter((c) => !isSuppressed(c, policy));
@@ -7556,8 +7641,8 @@ var init_engine = __esm({
7556
7641
  });
7557
7642
 
7558
7643
  // src/policy-load.ts
7559
- import { readFileSync as readFileSync5, existsSync as existsSync4 } from "node:fs";
7560
- import { isAbsolute as isAbsolute6, join as join8, posix as posix2 } from "node:path";
7644
+ import { readFileSync as readFileSync5, existsSync as existsSync5 } from "node:fs";
7645
+ import { isAbsolute as isAbsolute7, join as join8, posix as posix2 } from "node:path";
7561
7646
  function normalizeVersion(v, where = POLICY_FILE) {
7562
7647
  if (v === void 0 || v === null) return 1;
7563
7648
  if (typeof v !== "number" || !Number.isInteger(v) || v < 1) {
@@ -7566,7 +7651,7 @@ function normalizeVersion(v, where = POLICY_FILE) {
7566
7651
  return v;
7567
7652
  }
7568
7653
  function ledgerInsideRepo(ledger) {
7569
- if (!ledger.trim() || isAbsolute6(ledger) || /^[A-Za-z]:[\\/]/.test(ledger)) return false;
7654
+ if (!ledger.trim() || isAbsolute7(ledger) || /^[A-Za-z]:[\\/]/.test(ledger)) return false;
7570
7655
  const norm2 = posix2.normalize(ledger.replace(/\\/g, "/"));
7571
7656
  return norm2 !== ".." && !norm2.startsWith("../") && !norm2.startsWith("/");
7572
7657
  }
@@ -7717,14 +7802,14 @@ function parseOrThrow(src, where) {
7717
7802
  return parsePolicy(raw, where);
7718
7803
  }
7719
7804
  function loadPolicy(cwd = process.cwd()) {
7720
- const path = join8(cwd, POLICY_FILE);
7721
- if (!existsSync4(path)) return defaultPolicy();
7805
+ const path = join8(repoRoot(cwd), POLICY_FILE);
7806
+ if (!existsSync5(path)) return defaultPolicy();
7722
7807
  return parseOrThrow(readFileSync5(path, "utf8"), POLICY_FILE);
7723
7808
  }
7724
- function loadPolicyAt(rev, cwd) {
7725
- const src = fileAt(rev, POLICY_FILE, { cwd });
7809
+ function loadPolicyAt(rev2, cwd) {
7810
+ const src = fileAt(rev2, POLICY_FILE, { cwd: cwd === void 0 ? void 0 : repoRoot(cwd) });
7726
7811
  if (src == null) return null;
7727
- return parseOrThrow(src, `${POLICY_FILE} at ${rev}`);
7812
+ return parseOrThrow(src, `${POLICY_FILE} at ${rev2}`);
7728
7813
  }
7729
7814
  var PolicyError, SEVERITIES, isSeverity, isStringList, isMapping, TOP_LEVEL_KEYS;
7730
7815
  var init_policy_load = __esm({
@@ -7734,6 +7819,7 @@ var init_policy_load = __esm({
7734
7819
  init_policy();
7735
7820
  init_build();
7736
7821
  init_narrow();
7822
+ init_repo_context();
7737
7823
  PolicyError = class extends Error {
7738
7824
  };
7739
7825
  SEVERITIES = ["block", "warn"];
@@ -7746,7 +7832,7 @@ var init_policy_load = __esm({
7746
7832
 
7747
7833
  // src/signoff.ts
7748
7834
  import { createHash as createHash2 } from "node:crypto";
7749
- import { appendFileSync, mkdirSync, readFileSync as readFileSync6, existsSync as existsSync5 } from "node:fs";
7835
+ import { appendFileSync, mkdirSync, readFileSync as readFileSync6, existsSync as existsSync6 } from "node:fs";
7750
7836
  import { dirname as dirname2, join as join9 } from "node:path";
7751
7837
  function ledgerEntryFrom(value) {
7752
7838
  if (!isRecord(value)) return null;
@@ -7768,7 +7854,7 @@ function ledgerPath(cwd, policy) {
7768
7854
  }
7769
7855
  function readLedger(cwd, policy) {
7770
7856
  const p = ledgerPath(cwd, policy);
7771
- if (!existsSync5(p)) return [];
7857
+ if (!existsSync6(p)) return [];
7772
7858
  const out3 = [];
7773
7859
  for (const line of readFileSync6(p, "utf8").split("\n")) {
7774
7860
  if (!line.trim()) continue;
@@ -8131,27 +8217,28 @@ var init_report = __esm({
8131
8217
 
8132
8218
  // src/cli/check.ts
8133
8219
  function check(opts) {
8134
- const cwd = opts.cwd ?? process.cwd();
8135
- if (!isGitRepo(cwd)) {
8136
- process.stderr.write(`tamperward: ${cwd} is not inside a git repository \u2014 nothing to check
8220
+ const requested = opts.cwd ?? process.cwd();
8221
+ if (!isGitRepo(requested)) {
8222
+ process.stderr.write(`tamperward: ${requested} is not inside a git repository \u2014 nothing to check
8137
8223
  `);
8138
8224
  return 2;
8139
8225
  }
8140
- let policy = opts.policyOverride ?? loadPolicy(opts.cwd);
8226
+ const cwd = repoRoot(requested);
8227
+ let policy = opts.policyOverride ?? loadPolicy(cwd);
8141
8228
  let changes;
8142
8229
  let layer;
8143
8230
  let view;
8144
8231
  if (opts.staged) {
8145
- changes = diffStaged({ cwd: opts.cwd });
8232
+ changes = diffStaged({ cwd });
8146
8233
  layer = "local";
8147
8234
  view = "staged";
8148
8235
  } else if (opts.worktree) {
8149
8236
  const p = policy;
8150
- changes = opts.includeUntracked ? diffWorktreeWithUntracked({ cwd: opts.cwd }, (rel) => isProtected(rel, p)) : diffWorktree({ cwd: opts.cwd });
8237
+ changes = opts.includeUntracked ? diffWorktreeWithUntracked({ cwd }, (rel) => isProtected(rel, p)) : diffWorktree({ cwd });
8151
8238
  const seen = new Set(changes.filter((c) => c.kind === "file").map((c) => c.path));
8152
- for (const rel of hiddenTrackedPaths({ cwd: opts.cwd })) {
8239
+ for (const rel of hiddenTrackedPaths({ cwd })) {
8153
8240
  if (seen.has(rel) || !isProtected(rel, p)) continue;
8154
- changes.push(...synthFileChange(rel, fileAt("HEAD", rel, { cwd: opts.cwd }), fileOnDisk(rel, { cwd: opts.cwd })));
8241
+ changes.push(...synthFileChange(rel, fileAt("HEAD", rel, { cwd }), fileOnDisk(rel, { cwd })));
8155
8242
  }
8156
8243
  layer = opts.ciLayer ? "ci" : "local";
8157
8244
  view = "worktree";
@@ -8163,7 +8250,7 @@ function check(opts) {
8163
8250
  `);
8164
8251
  return 2;
8165
8252
  }
8166
- changes = diffRange(base, head, { cwd: opts.cwd });
8253
+ changes = diffRange(base, head, { cwd });
8167
8254
  if (changes.length === 0) {
8168
8255
  process.stderr.write(
8169
8256
  `tamperward: note: the range ${base}...${head} contains no changes \u2014 if this is not an empty pull request, check that the range resolves to the intended commits
@@ -8172,7 +8259,7 @@ function check(opts) {
8172
8259
  }
8173
8260
  layer = "ci";
8174
8261
  view = "range";
8175
- policy = opts.policyOverride ?? loadPolicyAt(mergeBaseOf(base, head, { cwd: opts.cwd }), opts.cwd) ?? defaultPolicy();
8262
+ policy = opts.policyOverride ?? loadPolicyAt(mergeBaseOf(base, head, { cwd }), cwd) ?? defaultPolicy();
8176
8263
  } else {
8177
8264
  process.stderr.write("tamperward: specify --staged, --worktree, or --diff <base>...<head>\n");
8178
8265
  return 2;
@@ -8215,6 +8302,7 @@ var init_check = __esm({
8215
8302
  init_disk();
8216
8303
  init_signoff();
8217
8304
  init_report();
8305
+ init_repo_context();
8218
8306
  }
8219
8307
  });
8220
8308
 
@@ -8244,7 +8332,7 @@ var init_deny = __esm({
8244
8332
  });
8245
8333
 
8246
8334
  // src/session.ts
8247
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
8335
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
8248
8336
  import { dirname as dirname3, join as join10 } from "node:path";
8249
8337
  function baselinePath(cwd, sessionId) {
8250
8338
  const gd = gitDir(cwd);
@@ -8256,7 +8344,7 @@ function turnBaseline(cwd, sessionId) {
8256
8344
  try {
8257
8345
  const p = baselinePath(cwd, sessionId);
8258
8346
  if (!p) return null;
8259
- if (existsSync6(p)) {
8347
+ if (existsSync7(p)) {
8260
8348
  const v = readFileSync7(p, "utf8").trim();
8261
8349
  if (SHA.test(v)) return v;
8262
8350
  }
@@ -8292,9 +8380,8 @@ var init_session = __esm({
8292
8380
 
8293
8381
  // src/effect.ts
8294
8382
  import { createHash as createHash3 } from "node:crypto";
8295
- import { existsSync as existsSync7, lstatSync as lstatSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync8, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
8296
- import { isAbsolute as isAbsolute7, join as join11 } from "node:path";
8297
- import { execFileSync as execFileSync5 } from "node:child_process";
8383
+ import { existsSync as existsSync8, lstatSync as lstatSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync8, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
8384
+ import { join as join11 } from "node:path";
8298
8385
  function ptreeFrom(value) {
8299
8386
  if (!isRecord(value)) return null;
8300
8387
  const out3 = {};
@@ -8307,17 +8394,7 @@ function ptreeFrom(value) {
8307
8394
  return out3;
8308
8395
  }
8309
8396
  function gitDir2(cwd) {
8310
- try {
8311
- const d = execFileSync5("git", ["rev-parse", "--git-dir"], {
8312
- cwd,
8313
- encoding: "utf8",
8314
- stdio: ["ignore", "pipe", "pipe"]
8315
- }).trim();
8316
- if (!d) return null;
8317
- return isAbsolute7(d) ? d : join11(cwd, d);
8318
- } catch {
8319
- return null;
8320
- }
8397
+ return repoContext(cwd)?.gitDir ?? null;
8321
8398
  }
8322
8399
  function statePath(cwd, sessionId, kind) {
8323
8400
  if (!sessionId) return null;
@@ -8339,10 +8416,11 @@ function entryOf(e) {
8339
8416
  const hash = e.content != null ? sha(e.content) : e.kind === "unreadable" ? "unreadable" : `${e.kind}:${e.detail}`;
8340
8417
  return { hash, mode: e.mode, size: e.size, mtimeMs: e.mtimeMs };
8341
8418
  }
8342
- function snapshotProtected(cwd, policy, prev, cache2) {
8419
+ function snapshotProtected(cwd, policy, prev, cache3) {
8343
8420
  void prev;
8344
- cache2?.beginSnapshot();
8345
- const entryAt = (rel) => cache2 ? cache2.entry(cwd, rel, () => entryOf(inspectRel(cwd, rel))) : entryOf(inspectRel(cwd, rel));
8421
+ cwd = repoRoot(cwd);
8422
+ cache3?.beginSnapshot();
8423
+ const entryAt = (rel) => cache3 ? cache3.entry(cwd, rel, () => entryOf(inspectRel(cwd, rel))) : entryOf(inspectRel(cwd, rel));
8346
8424
  const out3 = {};
8347
8425
  const keep = (rel) => isProtected(rel, policy);
8348
8426
  let pruned = /* @__PURE__ */ new Set();
@@ -8354,10 +8432,10 @@ function snapshotProtected(cwd, policy, prev, cache2) {
8354
8432
  } catch {
8355
8433
  }
8356
8434
  const walk2 = (rel) => {
8357
- const abs2 = rel ? join11(cwd, rel) : cwd;
8435
+ const abs = rel ? join11(cwd, rel) : cwd;
8358
8436
  let names;
8359
8437
  try {
8360
- names = readdirSync(abs2);
8438
+ names = readdirSync(abs);
8361
8439
  } catch {
8362
8440
  return;
8363
8441
  }
@@ -8394,7 +8472,7 @@ function driftBetween(expected, current) {
8394
8472
  return { changed, deleted, added };
8395
8473
  }
8396
8474
  function loadTree(p) {
8397
- if (!p || !existsSync7(p)) return null;
8475
+ if (!p || !existsSync8(p)) return null;
8398
8476
  try {
8399
8477
  return ptreeFrom(JSON.parse(readFileSync8(p, "utf8")));
8400
8478
  } catch {
@@ -8437,6 +8515,7 @@ var UNSAFE2, SKIP_DIRS2, sha;
8437
8515
  var init_effect = __esm({
8438
8516
  "src/effect.ts"() {
8439
8517
  "use strict";
8518
+ init_repo_context();
8440
8519
  init_policy();
8441
8520
  init_disk();
8442
8521
  init_build();
@@ -8451,8 +8530,8 @@ var init_effect = __esm({
8451
8530
  import { appendFileSync as appendFileSync3, lstatSync as lstatSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync9, readdirSync as readdirSync2, watch, writeFileSync as writeFileSync4 } from "node:fs";
8452
8531
  import { join as join12 } from "node:path";
8453
8532
  import { createHash as createHash4 } from "node:crypto";
8454
- function snap(abs2) {
8455
- const e = inspectPath(abs2);
8533
+ function snap(abs) {
8534
+ const e = inspectPath(abs);
8456
8535
  if (e.kind === "absent" || e.kind === "unreadable") return { mode: null, size: null, hash: null };
8457
8536
  if (e.kind !== "file" || e.content == null) return { mode: e.mode, size: null, hash: null };
8458
8537
  const hash = createHash4("sha256").update(e.content).digest("hex").slice(0, 16);
@@ -8968,10 +9047,10 @@ var init_fs_events = __esm({
8968
9047
  });
8969
9048
 
8970
9049
  // src/cli/hook.ts
8971
- import { readFileSync as readFileSync10, appendFileSync as appendFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "node:fs";
9050
+ import { readFileSync as readFileSync10, appendFileSync as appendFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync9 } from "node:fs";
8972
9051
  import { createHash as createHash5 } from "node:crypto";
8973
- function setSnapshotCache(cache2) {
8974
- snapshotCache = cache2;
9052
+ function setSnapshotCache(cache3) {
9053
+ snapshotCache = cache3;
8975
9054
  }
8976
9055
  function readStdin() {
8977
9056
  try {
@@ -9101,8 +9180,8 @@ function uncoveredDrift(cwd, policy, expected, current, covered, base, turnTree)
9101
9180
  }
9102
9181
  const want = expected[rel]?.hash;
9103
9182
  let before = null;
9104
- for (const rev of candidates) {
9105
- const blob = fileAt(rev, rel, { cwd });
9183
+ for (const rev2 of candidates) {
9184
+ const blob = fileAt(rev2, rel, { cwd });
9106
9185
  if (blob != null && contentHash(blob) === want) {
9107
9186
  before = blob;
9108
9187
  break;
@@ -9213,12 +9292,13 @@ function sanctionPredictedWrites(cwd, sessionId, policy, changes) {
9213
9292
  }
9214
9293
  function preToolUseVerdict(input, defaultCwd) {
9215
9294
  try {
9216
- const cwd = input.cwd ?? defaultCwd ?? process.cwd();
9295
+ const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
9296
+ const cwd = repoRoot(sessionCwd);
9217
9297
  turnBaseline(cwd, input.session_id);
9218
9298
  const policy = loadPolicy(cwd);
9219
9299
  const driftBlocks = effectDriftBlocks(cwd, input.session_id, policy);
9220
9300
  if (driftBlocks) return verdict(driftBlocks, "PreToolUse");
9221
- const changes = changesFromClaudeHook(input, cwd);
9301
+ const changes = changesFromClaudeHook(input, cwd, sessionCwd);
9222
9302
  const blocks2 = evaluate(changes, policy, void 0, "tool-call", { cwd }).filter((f) => f.severity === "block");
9223
9303
  if (blocks2.length === 0) sanctionPredictedWrites(cwd, input.session_id, policy, changes);
9224
9304
  return verdict(blocks2, "PreToolUse");
@@ -9250,9 +9330,9 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
9250
9330
  recordObserverHealth(telemetry.state, telemetry.reason);
9251
9331
  }
9252
9332
  const cp = cursorPath(cwd, sessionId);
9253
- if (!existsSync8(log)) return none;
9333
+ if (!existsSync9(log)) return none;
9254
9334
  let offset = 0;
9255
- if (cp && existsSync8(cp)) {
9335
+ if (cp && existsSync9(cp)) {
9256
9336
  const n = Number(readFileSync10(cp, "utf8"));
9257
9337
  offset = Number.isFinite(n) ? n : 0;
9258
9338
  }
@@ -9321,8 +9401,9 @@ function recordObserverHealth(state, reason) {
9321
9401
  }
9322
9402
  function stopVerdict(input, defaultCwd) {
9323
9403
  if (input.stop_hook_active) return { exitCode: 0, stdout: "" };
9324
- const cwd = input.cwd ?? defaultCwd ?? process.cwd();
9325
- if (!isGitRepo(cwd)) return { exitCode: 0, stdout: "" };
9404
+ const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
9405
+ if (!isGitRepo(sessionCwd)) return { exitCode: 0, stdout: "" };
9406
+ const cwd = repoRoot(sessionCwd);
9326
9407
  let blocks2;
9327
9408
  let commitCursor = () => {
9328
9409
  };
@@ -9410,6 +9491,7 @@ var init_hook = __esm({
9410
9491
  init_policy();
9411
9492
  init_disk();
9412
9493
  init_narrow();
9494
+ init_repo_context();
9413
9495
  snapshotCache = null;
9414
9496
  HookInputError = class extends Error {
9415
9497
  };
@@ -9468,22 +9550,22 @@ var init_ptree_cache = __esm({
9468
9550
  * time, the way the in-process path treats them.
9469
9551
  */
9470
9552
  entry(cwd, rel, compute) {
9471
- const abs2 = join13(cwd, rel);
9553
+ const abs = join13(cwd, rel);
9472
9554
  let st;
9473
9555
  try {
9474
- st = lstatSync5(abs2, { bigint: true });
9556
+ st = lstatSync5(abs, { bigint: true });
9475
9557
  } catch {
9476
- this.map.delete(abs2);
9558
+ this.map.delete(abs);
9477
9559
  this.stats.misses++;
9478
9560
  return compute();
9479
9561
  }
9480
9562
  if (!st.isFile()) {
9481
- this.map.delete(abs2);
9563
+ this.map.delete(abs);
9482
9564
  this.stats.misses++;
9483
9565
  return compute();
9484
9566
  }
9485
9567
  const key2 = keyOf(st);
9486
- const hit = this.map.get(abs2);
9568
+ const hit = this.map.get(abs);
9487
9569
  if (hit && hit.key === key2 && hit.recordedAtNs - hit.ctimeNs >= this.racyMarginNs) {
9488
9570
  this.stats.hits++;
9489
9571
  return hit.entry;
@@ -9493,17 +9575,17 @@ var init_ptree_cache = __esm({
9493
9575
  const entry = compute();
9494
9576
  let after;
9495
9577
  try {
9496
- after = lstatSync5(abs2, { bigint: true });
9578
+ after = lstatSync5(abs, { bigint: true });
9497
9579
  } catch {
9498
- this.map.delete(abs2);
9580
+ this.map.delete(abs);
9499
9581
  return entry;
9500
9582
  }
9501
9583
  if (!after.isFile() || keyOf(after) !== key2) {
9502
- this.map.delete(abs2);
9584
+ this.map.delete(abs);
9503
9585
  return entry;
9504
9586
  }
9505
9587
  if (this.map.size >= this.maxEntries) this.clear();
9506
- this.map.set(abs2, { key: key2, ctimeNs: st.ctimeNs, recordedAtNs, entry });
9588
+ this.map.set(abs, { key: key2, ctimeNs: st.ctimeNs, recordedAtNs, entry });
9507
9589
  return entry;
9508
9590
  }
9509
9591
  };
@@ -9511,7 +9593,7 @@ var init_ptree_cache = __esm({
9511
9593
  });
9512
9594
 
9513
9595
  // src/cli/hook-service.ts
9514
- import { chmodSync, existsSync as existsSync9, lstatSync as lstatSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync11, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
9596
+ import { chmodSync, existsSync as existsSync10, lstatSync as lstatSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync11, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
9515
9597
  import { createConnection as createConnection2, createServer } from "node:net";
9516
9598
  import { realpathSync as realpathSync3 } from "node:fs";
9517
9599
  import { sep } from "node:path";
@@ -9536,12 +9618,12 @@ function pidAlive2(pid) {
9536
9618
  }
9537
9619
  }
9538
9620
  function listening(socket) {
9539
- return new Promise((resolve15) => {
9621
+ return new Promise((resolve16) => {
9540
9622
  const sock = createConnection2(socket);
9541
9623
  const done = (v) => {
9542
9624
  clearTimeout(timer);
9543
9625
  sock.destroy();
9544
- resolve15(v);
9626
+ resolve16(v);
9545
9627
  };
9546
9628
  const timer = setTimeout(() => done(false), 1e3);
9547
9629
  sock.once("connect", () => done(true));
@@ -9599,7 +9681,7 @@ function removeQuietly(p) {
9599
9681
  }
9600
9682
  }
9601
9683
  function ensurePrivateDir(dir, uid) {
9602
- if (!existsSync9(dir)) mkdirSync5(dir, { recursive: true, mode: 448 });
9684
+ if (!existsSync10(dir)) mkdirSync5(dir, { recursive: true, mode: 448 });
9603
9685
  const st = lstatSync6(dir);
9604
9686
  if (!st.isDirectory()) throw new Error(`${dir} is not a directory`);
9605
9687
  if (st.uid !== uid) throw new Error(`${dir} is owned by uid ${st.uid}, not ${uid}; refusing to serve from it`);
@@ -9616,7 +9698,7 @@ async function startHookService(opts) {
9616
9698
  if (uid === void 0) throw new Error("no uid on this platform; refusing to start");
9617
9699
  let root;
9618
9700
  try {
9619
- root = realpathSync3(opts.root);
9701
+ root = realpathSync3(repoRoot(realpathSync3(opts.root)));
9620
9702
  } catch {
9621
9703
  throw new Error(`cannot resolve ${opts.root}`);
9622
9704
  }
@@ -9627,7 +9709,7 @@ async function startHookService(opts) {
9627
9709
  }
9628
9710
  removeQuietly(paths.socket);
9629
9711
  removeQuietly(paths.state);
9630
- const cache2 = opts.cache ?? new SnapshotCache();
9712
+ const cache3 = opts.cache ?? new SnapshotCache();
9631
9713
  let served = 0;
9632
9714
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
9633
9715
  const respond = (sock, body) => {
@@ -9653,7 +9735,7 @@ async function startHookService(opts) {
9653
9735
  root,
9654
9736
  started_at: startedAt,
9655
9737
  served,
9656
- cache: { hits: cache2.stats.hits, misses: cache2.stats.misses, size: cache2.size }
9738
+ cache: { hits: cache3.stats.hits, misses: cache3.stats.misses, size: cache3.size }
9657
9739
  });
9658
9740
  }
9659
9741
  if (!within(root, req2.cwd)) return respond(sock, { refused: "cwd outside the bound repository" });
@@ -9694,11 +9776,11 @@ async function startHookService(opts) {
9694
9776
  });
9695
9777
  const umask = process.umask(63);
9696
9778
  try {
9697
- await new Promise((resolve15, reject) => {
9779
+ await new Promise((resolve16, reject) => {
9698
9780
  server.once("error", reject);
9699
9781
  server.listen(paths.socket, () => {
9700
9782
  server.off("error", reject);
9701
- resolve15();
9783
+ resolve16();
9702
9784
  });
9703
9785
  });
9704
9786
  } finally {
@@ -9707,7 +9789,7 @@ async function startHookService(opts) {
9707
9789
  chmodSync(paths.socket, 384);
9708
9790
  const state = { pid: process.pid, version: TW_VERSION, root, started_at: startedAt };
9709
9791
  writeFileSync6(paths.state, JSON.stringify(state) + "\n", { mode: 384 });
9710
- setSnapshotCache(cache2);
9792
+ setSnapshotCache(cache3);
9711
9793
  let closed = false;
9712
9794
  return {
9713
9795
  paths,
@@ -9715,14 +9797,14 @@ async function startHookService(opts) {
9715
9797
  get served() {
9716
9798
  return served;
9717
9799
  },
9718
- close: () => new Promise((resolve15) => {
9719
- if (closed) return resolve15();
9800
+ close: () => new Promise((resolve16) => {
9801
+ if (closed) return resolve16();
9720
9802
  closed = true;
9721
9803
  setSnapshotCache(null);
9722
9804
  server.close(() => {
9723
9805
  removeQuietly(paths.socket);
9724
9806
  removeQuietly(paths.state);
9725
- resolve15();
9807
+ resolve16();
9726
9808
  });
9727
9809
  })
9728
9810
  };
@@ -9751,7 +9833,7 @@ async function stopHookService(paths) {
9751
9833
  process.kill(state.pid, "SIGTERM");
9752
9834
  } catch {
9753
9835
  }
9754
- const gone = () => !pidAlive2(state.pid) || !existsSync9(paths.socket);
9836
+ const gone = () => !pidAlive2(state.pid) || !existsSync10(paths.socket);
9755
9837
  const until = Date.now() + STOP_WAIT_MS;
9756
9838
  while (!gone() && Date.now() < until) sleepSync(50);
9757
9839
  if (!gone()) throw new Error(`hook service pid ${state.pid} did not exit within ${STOP_WAIT_MS / 1e3}s`);
@@ -9819,9 +9901,9 @@ function runHookService(args) {
9819
9901
  `);
9820
9902
  process.exit(0);
9821
9903
  }
9822
- const cache2 = isRecord(res.cache) ? res.cache : {};
9904
+ const cache3 = isRecord(res.cache) ? res.cache : {};
9823
9905
  process.stdout.write(
9824
- `tamperward hook-service: running (pid ${state.pid}, tamperward@${state.version}, root ${state.root}, since ${state.started_at}); served: ${res.served}; snapshot cache hits/misses/entries: ${String(cache2.hits ?? 0)}/${String(cache2.misses ?? 0)}/${String(cache2.size ?? 0)}; ${optIn}
9906
+ `tamperward hook-service: running (pid ${state.pid}, tamperward@${state.version}, root ${state.root}, since ${state.started_at}); served: ${res.served}; snapshot cache hits/misses/entries: ${String(cache3.hits ?? 0)}/${String(cache3.misses ?? 0)}/${String(cache3.size ?? 0)}; ${optIn}
9825
9907
  `
9826
9908
  );
9827
9909
  process.exit(0);
@@ -9835,6 +9917,7 @@ var init_hook_service = __esm({
9835
9917
  init_narrow();
9836
9918
  init_wiring();
9837
9919
  init_ptree_cache();
9920
+ init_repo_context();
9838
9921
  init_hook();
9839
9922
  init_hook_client();
9840
9923
  MAX_REQUEST_BYTES = 64 * 1024 * 1024;
@@ -9852,7 +9935,7 @@ function runAllow(opts) {
9852
9935
  process.stderr.write("tamperward: a --reason is required \u2014 sign-offs must be justified.\n");
9853
9936
  return 2;
9854
9937
  }
9855
- const cwd = opts.cwd ?? process.cwd();
9938
+ const cwd = repoRoot(opts.cwd ?? process.cwd());
9856
9939
  const policy = loadPolicy(cwd);
9857
9940
  let findings;
9858
9941
  try {
@@ -9894,6 +9977,7 @@ var init_allow = __esm({
9894
9977
  init_engine();
9895
9978
  init_policy_load();
9896
9979
  init_signoff();
9980
+ init_repo_context();
9897
9981
  }
9898
9982
  });
9899
9983
 
@@ -9934,8 +10018,8 @@ var init_verifier_limits = __esm({
9934
10018
  // src/cli/init.ts
9935
10019
  import { execFileSync as execFileSync6 } from "node:child_process";
9936
10020
  import { createHash as createHash6 } from "node:crypto";
9937
- import { existsSync as existsSync10, lstatSync as lstatSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync7, chmodSync as chmodSync2, statSync as statSync2 } from "node:fs";
9938
- import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute8, join as join14, relative as relative3, resolve as resolve3 } from "node:path";
10021
+ import { existsSync as existsSync11, lstatSync as lstatSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync12, realpathSync as realpathSync4, writeFileSync as writeFileSync7, chmodSync as chmodSync2, statSync as statSync2 } from "node:fs";
10022
+ import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute8, join as join14, relative as relative3, resolve as resolve4 } from "node:path";
9939
10023
  function gitConfig(cwd, key2) {
9940
10024
  try {
9941
10025
  return execFileSync6("git", ["config", "--get", key2], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
@@ -10038,7 +10122,7 @@ function verifierCandidates(cwd) {
10038
10122
  if (/^\s*\[tool\.pytest(?:\.ini_options)?\]\s*$/m.test(pyproject)) add("pytest");
10039
10123
  } catch {
10040
10124
  }
10041
- if (existsSync10(join14(cwd, "pytest.ini"))) add("pytest");
10125
+ if (existsSync11(join14(cwd, "pytest.ini"))) add("pytest");
10042
10126
  try {
10043
10127
  const setupCfg = readFileSync12(join14(cwd, "setup.cfg"), "utf8");
10044
10128
  if (/^\s*\[tool:pytest\]\s*$/m.test(setupCfg)) add("pytest");
@@ -10096,7 +10180,7 @@ function verifierSetupMessage(cwd) {
10096
10180
  }
10097
10181
  function planPolicy(cwd) {
10098
10182
  const path = join14(cwd, POLICY_FILE);
10099
- if (existsSync10(path)) {
10183
+ if (existsSync11(path)) {
10100
10184
  try {
10101
10185
  loadPolicy(cwd);
10102
10186
  } catch (e) {
@@ -10115,9 +10199,9 @@ function planPolicy(cwd) {
10115
10199
  function planGitignore(cwd) {
10116
10200
  const rel = ".gitignore";
10117
10201
  const path = join14(cwd, rel);
10118
- if (!existsSync10(join14(cwd, "node_modules"))) return null;
10202
+ if (!existsSync11(join14(cwd, "node_modules"))) return null;
10119
10203
  let existing = "";
10120
- if (existsSync10(path)) {
10204
+ if (existsSync11(path)) {
10121
10205
  let st;
10122
10206
  try {
10123
10207
  st = lstatSync7(path);
@@ -10166,7 +10250,7 @@ function planGitignore(cwd) {
10166
10250
  return {
10167
10251
  item: "gitignore",
10168
10252
  path: rel,
10169
- status: existsSync10(path) ? "update" : "create",
10253
+ status: existsSync11(path) ? "update" : "create",
10170
10254
  detail: "exclude installed Node dependencies from repository diffs",
10171
10255
  apply: () => writeFileSync7(path, next)
10172
10256
  };
@@ -10175,7 +10259,7 @@ function planClaudeHooks(cwd) {
10175
10259
  const rel = ".claude/settings.json";
10176
10260
  const path = join14(cwd, rel);
10177
10261
  let settings = {};
10178
- if (existsSync10(path)) {
10262
+ if (existsSync11(path)) {
10179
10263
  let parsed;
10180
10264
  try {
10181
10265
  parsed = JSON.parse(readFileSync12(path, "utf8"));
@@ -10213,7 +10297,7 @@ function planClaudeHooks(cwd) {
10213
10297
  return {
10214
10298
  item: "agent",
10215
10299
  path: rel,
10216
- status: existsSync10(path) ? "update" : "create",
10300
+ status: existsSync11(path) ? "update" : "create",
10217
10301
  detail: [
10218
10302
  needPre && "wire PreToolUse deny",
10219
10303
  needStop && "wire Stop sweep",
@@ -10247,10 +10331,10 @@ function planClaudeHooks(cwd) {
10247
10331
  function hooksDir(cwd) {
10248
10332
  try {
10249
10333
  const out3 = execFileSync6("git", ["rev-parse", "--git-path", "hooks"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
10250
- if (out3) return resolve3(cwd, out3);
10334
+ if (out3) return resolve4(cwd, out3);
10251
10335
  } catch {
10252
10336
  }
10253
- return existsSync10(join14(cwd, ".git")) ? join14(cwd, ".git", "hooks") : null;
10337
+ return existsSync11(join14(cwd, ".git")) ? join14(cwd, ".git", "hooks") : null;
10254
10338
  }
10255
10339
  function display(cwd, path) {
10256
10340
  const rel = relative3(cwd, path);
@@ -10277,11 +10361,11 @@ function planPreCommit(cwd) {
10277
10361
  const skip = (detail) => ({ item: "pre-commit", path: "(none)", status: "skip", detail });
10278
10362
  let path;
10279
10363
  let note;
10280
- if (existsSync10(join14(cwd, ".husky"))) {
10364
+ if (existsSync11(join14(cwd, ".husky"))) {
10281
10365
  path = join14(cwd, ".husky", "pre-commit");
10282
10366
  note = "husky";
10283
10367
  } else {
10284
- const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync10(join14(cwd, c))));
10368
+ const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync11(join14(cwd, c))));
10285
10369
  if (managed) {
10286
10370
  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}`);
10287
10371
  }
@@ -10297,10 +10381,10 @@ function planPreCommit(cwd) {
10297
10381
  }
10298
10382
  }
10299
10383
  const rel = display(cwd, path);
10300
- if (existsSync10(path) && !statSync2(path).isFile()) {
10384
+ if (existsSync11(path) && !statSync2(path).isFile()) {
10301
10385
  return { item: "pre-commit", path: rel, status: "error", detail: "exists but is not a regular file \u2014 refusing to write" };
10302
10386
  }
10303
- const existing = existsSync10(path) ? readFileSync12(path, "utf8") : null;
10387
+ const existing = existsSync11(path) ? readFileSync12(path, "utf8") : null;
10304
10388
  if (existing === null) {
10305
10389
  return {
10306
10390
  item: "pre-commit",
@@ -10389,7 +10473,7 @@ ${block.join("\n")}
10389
10473
  function planCodeowners(cwd) {
10390
10474
  const rel = ".github/CODEOWNERS";
10391
10475
  const existingRel = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"].find(
10392
- (r) => existsSync10(join14(cwd, r))
10476
+ (r) => existsSync11(join14(cwd, r))
10393
10477
  );
10394
10478
  const path = join14(cwd, existingRel ?? rel);
10395
10479
  const existing = existingRel ? readFileSync12(path, "utf8") : null;
@@ -10430,7 +10514,7 @@ function planWorkflow(cwd, force) {
10430
10514
  mkdirSync6(dirname4(path), { recursive: true });
10431
10515
  writeFileSync7(path, workflowFile(WORKFLOW_CONTENT));
10432
10516
  };
10433
- if (!existsSync10(path)) {
10517
+ if (!existsSync11(path)) {
10434
10518
  return {
10435
10519
  item: "ci",
10436
10520
  path: rel,
@@ -10482,6 +10566,13 @@ function planned(item, path, plan) {
10482
10566
  return { item, path, status: "error", detail: `cannot plan this item \u2014 ${errText3(e)}` };
10483
10567
  }
10484
10568
  }
10569
+ function sameDir(a, b) {
10570
+ try {
10571
+ return realpathSync4(a) === realpathSync4(b);
10572
+ } catch {
10573
+ return false;
10574
+ }
10575
+ }
10485
10576
  function planInit(cwd, opts = {}) {
10486
10577
  requireShippedVersion();
10487
10578
  const gitignore = planGitignore(cwd);
@@ -10495,9 +10586,14 @@ function planInit(cwd, opts = {}) {
10495
10586
  ];
10496
10587
  }
10497
10588
  function runInit(opts) {
10498
- const cwd = opts.cwd ?? process.cwd();
10499
- const plan = planInit(cwd, { forceWorkflow: opts.forceWorkflow });
10589
+ const requested = resolve4(opts.cwd ?? process.cwd());
10590
+ const cwd = repoRoot(requested);
10500
10591
  const w = opts.quiet ? { write: (_text) => true } : process.stdout;
10592
+ if (cwd !== requested && !sameDir(cwd, requested)) {
10593
+ process.stderr.write(`tamperward init: ${requested} is inside the repository rooted at ${cwd}; wiring the root.
10594
+ `);
10595
+ }
10596
+ const plan = planInit(cwd, { forceWorkflow: opts.forceWorkflow });
10501
10597
  let applied = 0;
10502
10598
  for (const a of plan) {
10503
10599
  if (!opts.dryRun && a.apply) {
@@ -10552,6 +10648,7 @@ var init_init = __esm({
10552
10648
  init_verifier_limits();
10553
10649
  init_wiring();
10554
10650
  init_narrow();
10651
+ init_repo_context();
10555
10652
  errText3 = (e) => (e instanceof Error ? e.message : String(e)).replace(/\s+/g, " ").trim();
10556
10653
  isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
10557
10654
  HOOK_RE = /\btamperward(?:@\S+)?\s+hook\s+claude\b/;
@@ -10720,12 +10817,12 @@ var init_fingerprint = __esm({
10720
10817
  // src/dependency-env.ts
10721
10818
  import { createHash as createHash8 } from "node:crypto";
10722
10819
  import {
10723
- existsSync as existsSync11,
10820
+ existsSync as existsSync12,
10724
10821
  lstatSync as lstatSync8,
10725
10822
  readdirSync as readdirSync3,
10726
10823
  readFileSync as readFileSync13,
10727
10824
  readlinkSync as readlinkSync2,
10728
- realpathSync as realpathSync4,
10825
+ realpathSync as realpathSync5,
10729
10826
  statSync as statSync3
10730
10827
  } from "node:fs";
10731
10828
  import {
@@ -10735,14 +10832,14 @@ import {
10735
10832
  isAbsolute as isAbsolute9,
10736
10833
  join as join15,
10737
10834
  relative as relative4,
10738
- resolve as resolve4,
10835
+ resolve as resolve5,
10739
10836
  sep as sep2
10740
10837
  } from "node:path";
10741
10838
  function metricsFor(descriptor) {
10742
10839
  return descriptor.metrics ??= { fullSnapshots: 0, reusedSnapshots: 0, totalMs: 0 };
10743
10840
  }
10744
10841
  function inside(root, candidate) {
10745
- const rel = relative4(resolve4(root), resolve4(candidate));
10842
+ const rel = relative4(resolve5(root), resolve5(candidate));
10746
10843
  return rel === "" || rel !== ".." && !rel.startsWith(".." + sep2) && !isAbsolute9(rel);
10747
10844
  }
10748
10845
  function modeKind(path) {
@@ -10757,7 +10854,7 @@ function fingerprintRoot(root, cwd, h) {
10757
10854
  let currentReal;
10758
10855
  try {
10759
10856
  rootStat = lstatSync8(root.path);
10760
- currentReal = realpathSync4(root.path);
10857
+ currentReal = realpathSync5(root.path);
10761
10858
  } catch (e) {
10762
10859
  throw new Unattestable(
10763
10860
  root.kind + " root " + JSON.stringify(root.path) + " can no longer be resolved (" + (e instanceof Error ? e.message : String(e)) + ")"
@@ -10773,7 +10870,7 @@ function fingerprintRoot(root, cwd, h) {
10773
10870
  }
10774
10871
  h.update("root\0" + root.kind + "\0" + root.path + "\0" + root.realPath + "\0" + String(rootStat.mode) + "\0");
10775
10872
  if (rootStat.isSymbolicLink()) h.update(readlinkSync2(root.path) + "\0");
10776
- const cwdReal = realpathSync4(cwd);
10873
+ const cwdReal = realpathSync5(cwd);
10777
10874
  const visited = /* @__PURE__ */ new Set();
10778
10875
  const hashFile = (path, label) => {
10779
10876
  let st;
@@ -10790,7 +10887,7 @@ function fingerprintRoot(root, cwd, h) {
10790
10887
  }
10791
10888
  };
10792
10889
  const walk2 = (dir, namespace) => {
10793
- const realDir = realpathSync4(dir);
10890
+ const realDir = realpathSync5(dir);
10794
10891
  if (visited.has(realDir)) return;
10795
10892
  visited.add(realDir);
10796
10893
  let entries;
@@ -10840,7 +10937,7 @@ function fingerprintRoot(root, cwd, h) {
10840
10937
  h.update(target + "\0");
10841
10938
  let resolved;
10842
10939
  try {
10843
- resolved = realpathSync4(path);
10940
+ resolved = realpathSync5(path);
10844
10941
  } catch {
10845
10942
  continue;
10846
10943
  }
@@ -10874,7 +10971,7 @@ function snapshot(roots, probes, cwd) {
10874
10971
  const h = createHash8("sha256");
10875
10972
  for (const probe of probes) {
10876
10973
  h.update(
10877
- "probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync11(probe.path) ? "present" : "absent") + "\0"
10974
+ "probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync12(probe.path) ? "present" : "absent") + "\0"
10878
10975
  );
10879
10976
  }
10880
10977
  for (const root of roots) fingerprintRoot(root, cwd, h);
@@ -10895,7 +10992,7 @@ function freezeRoot(kind, path) {
10895
10992
  if (!statSync3(path).isDirectory()) {
10896
10993
  throw new Error("not a directory");
10897
10994
  }
10898
- return { kind, path: resolve4(path), realPath: realpathSync4(path) };
10995
+ return { kind, path: resolve5(path), realPath: realpathSync5(path) };
10899
10996
  } catch (e) {
10900
10997
  throw new Unattestable(
10901
10998
  kind + " root " + JSON.stringify(path) + " is not resolvable (" + (e instanceof Error ? e.message : String(e)) + ")"
@@ -10931,17 +11028,17 @@ function firstExecutable(command) {
10931
11028
  function resolveExecutable(token, cwd, env) {
10932
11029
  if (!token) return null;
10933
11030
  if (token.includes("/") || token.includes("\\")) {
10934
- const path = isAbsolute9(token) ? token : resolve4(cwd, token);
11031
+ const path = isAbsolute9(token) ? token : resolve5(cwd, token);
10935
11032
  try {
10936
- return existsSync11(path) ? realpathSync4(path) : null;
11033
+ return existsSync12(path) ? realpathSync5(path) : null;
10937
11034
  } catch {
10938
11035
  return null;
10939
11036
  }
10940
11037
  }
10941
11038
  for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
10942
- const candidate = resolve4(entry, token);
11039
+ const candidate = resolve5(entry, token);
10943
11040
  try {
10944
- if (existsSync11(candidate) && statSync3(candidate).isFile()) return realpathSync4(candidate);
11041
+ if (existsSync12(candidate) && statSync3(candidate).isFile()) return realpathSync5(candidate);
10945
11042
  } catch {
10946
11043
  }
10947
11044
  }
@@ -10958,13 +11055,13 @@ function looksPython(command, executable, token) {
10958
11055
  }
10959
11056
  function inferredVenvFromToken(token, cwd) {
10960
11057
  if (!token || !token.includes("/") && !token.includes("\\")) return null;
10961
- const path = isAbsolute9(token) ? token : resolve4(cwd, token);
11058
+ const path = isAbsolute9(token) ? token : resolve5(cwd, token);
10962
11059
  if (!pythonName(basename3(path))) return null;
10963
11060
  const parent = dirname5(path);
10964
11061
  const parentName = basename3(parent).toLowerCase();
10965
11062
  if (parentName !== "bin" && parentName !== "scripts") return null;
10966
11063
  const root = dirname5(parent);
10967
- return existsSync11(join15(root, "pyvenv.cfg")) ? root : null;
11064
+ return existsSync12(join15(root, "pyvenv.cfg")) ? root : null;
10968
11065
  }
10969
11066
  function unsupportedEcosystem(command, token) {
10970
11067
  const name = token ? basename3(token).toLowerCase() : "";
@@ -10980,11 +11077,11 @@ function inferredVenv(executable) {
10980
11077
  const parentName = basename3(parent).toLowerCase();
10981
11078
  if (parentName !== "bin" && parentName !== "scripts") return null;
10982
11079
  const root = dirname5(parent);
10983
- return existsSync11(join15(root, "pyvenv.cfg")) ? root : null;
11080
+ return existsSync12(join15(root, "pyvenv.cfg")) ? root : null;
10984
11081
  }
10985
11082
  function venvProblem(root) {
10986
11083
  const cfg = join15(root, "pyvenv.cfg");
10987
- if (!existsSync11(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
11084
+ if (!existsSync12(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
10988
11085
  try {
10989
11086
  const body = readFileSync13(cfg, "utf8");
10990
11087
  if (/^\s*include-system-site-packages\s*=\s*true\s*$/im.test(body)) {
@@ -10996,7 +11093,7 @@ function venvProblem(root) {
10996
11093
  return null;
10997
11094
  }
10998
11095
  function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
10999
- const cwd = resolve4(cwdInput);
11096
+ const cwd = resolve5(cwdInput);
11000
11097
  const roots = [];
11001
11098
  const probes = [];
11002
11099
  const seen = /* @__PURE__ */ new Set();
@@ -11011,7 +11108,7 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
11011
11108
  };
11012
11109
  try {
11013
11110
  const nodeModules = join15(cwd, "node_modules");
11014
- if (existsSync11(nodeModules)) addRoot("node_modules", nodeModules);
11111
+ if (existsSync12(nodeModules)) addRoot("node_modules", nodeModules);
11015
11112
  else probes.push({ kind: "node_modules", path: nodeModules });
11016
11113
  const token = firstExecutable(command);
11017
11114
  const executable = resolveExecutable(token, cwd, env);
@@ -11020,10 +11117,10 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
11020
11117
  if (!isAbsolute9(env.VIRTUAL_ENV)) {
11021
11118
  throw new Unattestable("VIRTUAL_ENV is relative (" + JSON.stringify(env.VIRTUAL_ENV) + ")");
11022
11119
  }
11023
- venv = resolve4(env.VIRTUAL_ENV);
11120
+ venv = resolve5(env.VIRTUAL_ENV);
11024
11121
  } else {
11025
11122
  venv = inferredVenv(executable) ?? inferredVenvFromToken(token, cwd);
11026
- if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync11(join15(cwd, ".venv", "pyvenv.cfg"))) {
11123
+ if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync12(join15(cwd, ".venv", "pyvenv.cfg"))) {
11027
11124
  venv = join15(cwd, ".venv");
11028
11125
  }
11029
11126
  }
@@ -11060,7 +11157,7 @@ function checkDependencyEnvironment(cwdInput, descriptor) {
11060
11157
  const fingerprint2 = measuredSnapshot(
11061
11158
  descriptor.roots,
11062
11159
  descriptor.probes,
11063
- resolve4(cwdInput),
11160
+ resolve5(cwdInput),
11064
11161
  metricsFor(descriptor)
11065
11162
  );
11066
11163
  if (fingerprint2 !== descriptor.fingerprint) {
@@ -11073,7 +11170,7 @@ function checkDependencyEnvironment(cwdInput, descriptor) {
11073
11170
  }
11074
11171
  function attestDependencyEnvironment(cwdInput, descriptor) {
11075
11172
  const attestation = {
11076
- cwd: resolve4(cwdInput),
11173
+ cwd: resolve5(cwdInput),
11077
11174
  descriptor,
11078
11175
  check: checkDependencyEnvironment(cwdInput, descriptor)
11079
11176
  };
@@ -11081,7 +11178,7 @@ function attestDependencyEnvironment(cwdInput, descriptor) {
11081
11178
  return attestation;
11082
11179
  }
11083
11180
  function reuseDependencyEnvironmentAttestation(cwdInput, descriptor, attestation) {
11084
- if (!attestation || !issuedAttestations.has(attestation) || attestation.descriptor !== descriptor || attestation.cwd !== resolve4(cwdInput)) return null;
11181
+ if (!attestation || !issuedAttestations.has(attestation) || attestation.descriptor !== descriptor || attestation.cwd !== resolve5(cwdInput)) return null;
11085
11182
  metricsFor(descriptor).reusedSnapshots += 1;
11086
11183
  return attestation.check;
11087
11184
  }
@@ -11447,8 +11544,8 @@ if (child) {
11447
11544
  // src/verifier-backend.ts
11448
11545
  import { createHash as createHash9, randomUUID } from "node:crypto";
11449
11546
  import { spawnSync as spawnSync2 } from "node:child_process";
11450
- import { accessSync, constants as constants2, readFileSync as readFileSync15, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
11451
- import { delimiter as delimiter2, dirname as dirname6, join as join17, resolve as resolve5 } from "node:path";
11547
+ import { accessSync, constants as constants2, readFileSync as readFileSync15, realpathSync as realpathSync6, statSync as statSync4 } from "node:fs";
11548
+ import { delimiter as delimiter2, dirname as dirname6, join as join17, resolve as resolve6 } from "node:path";
11452
11549
  function identity() {
11453
11550
  const uid = typeof process.getuid === "function" ? process.getuid() : null;
11454
11551
  const groups = typeof process.getgroups === "function" ? process.getgroups() : [];
@@ -11468,7 +11565,7 @@ function writableByCaller(path) {
11468
11565
  }
11469
11566
  }
11470
11567
  function replaceableByCaller(path) {
11471
- let current = resolve5(path);
11568
+ let current = resolve6(path);
11472
11569
  if (writableByCaller(current)) return true;
11473
11570
  current = dirname6(current);
11474
11571
  while (true) {
@@ -11485,7 +11582,7 @@ function findExecutable(name) {
11485
11582
  const candidate = join17(raw, name);
11486
11583
  try {
11487
11584
  accessSync(candidate, constants2.X_OK);
11488
- return realpathSync5(candidate);
11585
+ return realpathSync6(candidate);
11489
11586
  } catch {
11490
11587
  }
11491
11588
  }
@@ -11560,7 +11657,7 @@ function inspectImageMetadata(enginePath, host, image) {
11560
11657
  function engineStable(backend) {
11561
11658
  if (!backend.enginePath || !backend.engineSha256) return false;
11562
11659
  try {
11563
- if (realpathSync5(backend.enginePath) !== backend.enginePath) return false;
11660
+ if (realpathSync6(backend.enginePath) !== backend.enginePath) return false;
11564
11661
  if (replaceableByCaller(backend.enginePath)) return false;
11565
11662
  return fileSha256(backend.enginePath) === backend.engineSha256;
11566
11663
  } catch {
@@ -11814,7 +11911,7 @@ function runContainerStage(backend, dir, command, budgetSecs) {
11814
11911
  const args = containerRunArgs({
11815
11912
  image: backend.image,
11816
11913
  name,
11817
- workspace: resolve5(dir),
11914
+ workspace: resolve6(dir),
11818
11915
  command,
11819
11916
  uid,
11820
11917
  gid,
@@ -11926,9 +12023,9 @@ var init_verifier_backend = __esm({
11926
12023
  // src/cli/verify.ts
11927
12024
  import { execFileSync as execFileSync8, spawnSync as spawnSync3 } from "node:child_process";
11928
12025
  import { createHash as createHash10 } from "node:crypto";
11929
- import { chmodSync as chmodSync3, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync5, symlinkSync, writeFileSync as writeFileSync9, existsSync as existsSync12 } from "node:fs";
12026
+ import { chmodSync as chmodSync3, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync5, symlinkSync, writeFileSync as writeFileSync9, existsSync as existsSync13 } from "node:fs";
11930
12027
  import { tmpdir as tmpdir4 } from "node:os";
11931
- import { dirname as dirname7, isAbsolute as isAbsolute10, join as join18, relative as relative5, resolve as resolve6, sep as sep3 } from "node:path";
12028
+ import { dirname as dirname7, isAbsolute as isAbsolute10, join as join18, relative as relative5, resolve as resolve7, sep as sep3 } from "node:path";
11932
12029
  function oracleAssuranceReport() {
11933
12030
  return {
11934
12031
  level: "suite-exit-only",
@@ -11942,11 +12039,11 @@ function git2(args, cwd) {
11942
12039
  }
11943
12040
  function resolveBase(base, cwd) {
11944
12041
  assertRev(base);
11945
- const rev = git2(["rev-parse", "--verify", `${base}^{commit}`], cwd).trim();
12042
+ const rev2 = git2(["rev-parse", "--verify", `${base}^{commit}`], cwd).trim();
11946
12043
  try {
11947
- return git2(["merge-base", rev, "HEAD"], cwd).trim();
12044
+ return git2(["merge-base", rev2, "HEAD"], cwd).trim();
11948
12045
  } catch {
11949
- return rev;
12046
+ return rev2;
11950
12047
  }
11951
12048
  }
11952
12049
  function baseIsAncestorOfHead(base, cwd) {
@@ -11954,7 +12051,7 @@ function baseIsAncestorOfHead(base, cwd) {
11954
12051
  return r.status === 0;
11955
12052
  }
11956
12053
  function inside2(root, path) {
11957
- const rel = relative5(resolve6(root), resolve6(path));
12054
+ const rel = relative5(resolve7(root), resolve7(path));
11958
12055
  return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute10(rel);
11959
12056
  }
11960
12057
  function linkParts(path) {
@@ -11971,7 +12068,7 @@ function rootedLinkTarget(target) {
11971
12068
  return isAbsolute10(target) || driveRelativeLinkTarget(target);
11972
12069
  }
11973
12070
  function safeSymlink(target, out3, root, label) {
11974
- if (rootedLinkTarget(target) || !inside2(root, resolve6(dirname7(out3), target))) {
12071
+ if (rootedLinkTarget(target) || !inside2(root, resolve7(dirname7(out3), target))) {
11975
12072
  throw linkEscape(label, target);
11976
12073
  }
11977
12074
  rmSync5(out3, { force: true });
@@ -12022,7 +12119,7 @@ function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
12022
12119
  throw linkEscape(label, target);
12023
12120
  }
12024
12121
  stack = [];
12025
- pending.unshift(...linkParts(relative5(dependencyRoot, resolve6(next))));
12122
+ pending.unshift(...linkParts(relative5(dependencyRoot, resolve7(next))));
12026
12123
  } else {
12027
12124
  pending.unshift(...linkParts(next));
12028
12125
  }
@@ -12083,7 +12180,7 @@ function probeFilesystemCaseSensitivity(dir) {
12083
12180
  const lower = join18(dir, ".tw-case-probe-a");
12084
12181
  try {
12085
12182
  writeFileSync9(upper, "", { flag: "wx" });
12086
- return !existsSync12(lower);
12183
+ return !existsSync13(lower);
12087
12184
  } finally {
12088
12185
  rmSync5(upper, { force: true });
12089
12186
  }
@@ -12801,9 +12898,9 @@ var init_verify = __esm({
12801
12898
  // src/cli/run.ts
12802
12899
  import { execFileSync as execFileSync9, spawn, spawnSync as spawnSync4 } from "node:child_process";
12803
12900
  import { randomBytes } from "node:crypto";
12804
- import { accessSync as accessSync2, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync7, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync10 } from "node:fs";
12901
+ import { accessSync as accessSync2, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync10 } from "node:fs";
12805
12902
  import { tmpdir as tmpdir5 } from "node:os";
12806
- import { dirname as dirname8, join as join19, resolve as resolve7 } from "node:path";
12903
+ import { dirname as dirname8, join as join19, resolve as resolve8 } from "node:path";
12807
12904
  function authoritativeRunLifecyclePlatform(platform = process.platform) {
12808
12905
  return platform === "linux";
12809
12906
  }
@@ -12856,7 +12953,7 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
12856
12953
  }
12857
12954
  for (const candidate of candidates) {
12858
12955
  try {
12859
- const real = realpathSync7(candidate);
12956
+ const real = realpathSync8(candidate);
12860
12957
  const st = statSync5(real);
12861
12958
  if (!st.isFile() || writableByCaller2(real)) continue;
12862
12959
  const probe = spawnSync4(
@@ -13022,7 +13119,7 @@ function startSupervisedObserver(cwd, base, entry) {
13022
13119
  try {
13023
13120
  child = spawn(
13024
13121
  process.execPath,
13025
- [resolve7(cliEntry), "watch", "--dir", cwd, "--log", log, "--base", base],
13122
+ [resolve8(cliEntry), "watch", "--dir", cwd, "--log", log, "--base", base],
13026
13123
  {
13027
13124
  cwd,
13028
13125
  stdio: ["ignore", "ignore", "inherit"],
@@ -13130,7 +13227,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
13130
13227
  const out3 = [];
13131
13228
  let real;
13132
13229
  try {
13133
- real = realpathSync7(cwd);
13230
+ real = realpathSync8(cwd);
13134
13231
  } catch {
13135
13232
  return out3;
13136
13233
  }
@@ -13159,7 +13256,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
13159
13256
  for (const link of procLinks) {
13160
13257
  try {
13161
13258
  const raw = readlinkSync4(link).replace(/ \(deleted\)$/, "");
13162
- const held = raw.startsWith("/") ? resolve7(raw) : raw;
13259
+ const held = raw.startsWith("/") ? resolve8(raw) : raw;
13163
13260
  if (held === real || held.startsWith(real + "/")) {
13164
13261
  holds = true;
13165
13262
  break;
@@ -13172,7 +13269,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
13172
13269
  return out3;
13173
13270
  }
13174
13271
  function runEnvelope(opts) {
13175
- const cwd = resolve7(opts.cwd ?? process.cwd());
13272
+ const cwd = resolve8(opts.cwd ?? process.cwd());
13176
13273
  const say = opts.json ? (_s) => {
13177
13274
  } : out;
13178
13275
  if (!opts.argv.length) {
@@ -13716,8 +13813,8 @@ raise SystemExit(0)
13716
13813
 
13717
13814
  // src/cli/doctor.ts
13718
13815
  import { execFileSync as execFileSync10 } from "node:child_process";
13719
- import { existsSync as existsSync13, readFileSync as readFileSync18, readdirSync as readdirSync5 } from "node:fs";
13720
- import { join as join20, resolve as resolve8 } from "node:path";
13816
+ import { existsSync as existsSync14, readFileSync as readFileSync18, readdirSync as readdirSync5 } from "node:fs";
13817
+ import { join as join20, resolve as resolve9 } from "node:path";
13721
13818
  function lifecyclePlatformCheck(platform = process.platform, linuxPython = platform === "linux" ? trustedLinuxPython() : null) {
13722
13819
  if (platform === "linux") {
13723
13820
  if (linuxPython?.path) {
@@ -13772,7 +13869,7 @@ function wiringState(status, authority) {
13772
13869
  return authority ? "BROKEN" : "WARN";
13773
13870
  }
13774
13871
  function pinsInFile(path) {
13775
- if (!existsSync13(path)) return [];
13872
+ if (!existsSync14(path)) return [];
13776
13873
  let src = "";
13777
13874
  try {
13778
13875
  src = readFileSync18(path, "utf8");
@@ -13795,8 +13892,8 @@ function workflowPermissionCheck(cwd, workflowRels = [".github/workflows/tamperw
13795
13892
  const broken = [];
13796
13893
  const warnings = [];
13797
13894
  for (const rel of workflowRels) {
13798
- const path = resolve8(cwd, rel);
13799
- if (!existsSync13(path)) {
13895
+ const path = resolve9(cwd, rel);
13896
+ if (!existsSync14(path)) {
13800
13897
  broken.push(`${rel} is missing`);
13801
13898
  continue;
13802
13899
  }
@@ -14142,7 +14239,7 @@ function verifyJobs(doc) {
14142
14239
  return found;
14143
14240
  }
14144
14241
  function diagnose(opts = {}) {
14145
- const cwd = resolve8(opts.cwd ?? process.cwd());
14242
+ const cwd = repoRoot(resolve9(opts.cwd ?? process.cwd()));
14146
14243
  let policy;
14147
14244
  try {
14148
14245
  policy = policyFor(opts, cwd);
@@ -14166,8 +14263,8 @@ function diagnose(opts = {}) {
14166
14263
  workflowRels.push(opts.workflow);
14167
14264
  } else {
14168
14265
  const workflowDirRel = ".github/workflows";
14169
- const workflowDir = resolve8(cwd, workflowDirRel);
14170
- if (!existsSync13(workflowDir)) {
14266
+ const workflowDir = resolve9(cwd, workflowDirRel);
14267
+ if (!existsSync14(workflowDir)) {
14171
14268
  return fail("ci-verifier", `${workflowDirRel}: workflow directory does not exist`);
14172
14269
  }
14173
14270
  let entries;
@@ -14186,8 +14283,8 @@ function diagnose(opts = {}) {
14186
14283
  let verifyJobCount = 0;
14187
14284
  const authorityWorkflows = [];
14188
14285
  for (const workflowRel of workflowRels) {
14189
- const workflowPath = resolve8(cwd, workflowRel);
14190
- if (!existsSync13(workflowPath)) {
14286
+ const workflowPath = resolve9(cwd, workflowRel);
14287
+ if (!existsSync14(workflowPath)) {
14191
14288
  return fail("ci-verifier", `${workflowRel}: workflow does not exist`);
14192
14289
  }
14193
14290
  let doc;
@@ -14329,6 +14426,7 @@ var init_doctor = __esm({
14329
14426
  init_run();
14330
14427
  init_machine_output();
14331
14428
  init_narrow();
14429
+ init_repo_context();
14332
14430
  VERIFY_COMMAND = /\btamperward(?:@\S+)?\s+verify\b/;
14333
14431
  GITHUB_API_SCRIPT = [
14334
14432
  "const endpoint = process.argv[1];",
@@ -14347,16 +14445,16 @@ var init_doctor = __esm({
14347
14445
  // src/cli/trace-verify.ts
14348
14446
  import { execFileSync as execFileSync11, spawnSync as spawnSync5 } from "node:child_process";
14349
14447
  import {
14350
- existsSync as existsSync14,
14448
+ existsSync as existsSync15,
14351
14449
  mkdtempSync as mkdtempSync5,
14352
14450
  readFileSync as readFileSync19,
14353
14451
  readdirSync as readdirSync6,
14354
- realpathSync as realpathSync8,
14452
+ realpathSync as realpathSync9,
14355
14453
  rmSync as rmSync7,
14356
14454
  symlinkSync as symlinkSync2
14357
14455
  } from "node:fs";
14358
14456
  import { tmpdir as tmpdir6 } from "node:os";
14359
- import { isAbsolute as isAbsolute11, join as join21, relative as relative6, resolve as resolve9, sep as sep4 } from "node:path";
14457
+ import { isAbsolute as isAbsolute11, join as join21, relative as relative6, resolve as resolve10, sep as sep4 } from "node:path";
14360
14458
  function unescapeStraceString(s) {
14361
14459
  return s.replace(/\\([0-7]{1,3})/g, (_m, oct) => String.fromCharCode(parseInt(oct, 8))).replace(/\\x([0-9a-fA-F]{2})/g, (_m, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
14362
14460
  }
@@ -14381,13 +14479,13 @@ function parseStraceFileAccess(raw) {
14381
14479
  return out3;
14382
14480
  }
14383
14481
  function inside3(root, path) {
14384
- const rel = relative6(resolve9(root), resolve9(path));
14482
+ const rel = relative6(resolve10(root), resolve10(path));
14385
14483
  return rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !isAbsolute11(rel);
14386
14484
  }
14387
14485
  function normalRepoPath(root, raw) {
14388
- const abs2 = isAbsolute11(raw) ? resolve9(raw) : resolve9(root, raw);
14389
- if (!inside3(root, abs2)) return null;
14390
- const rel = relative6(root, abs2).split(sep4).join("/");
14486
+ const abs = isAbsolute11(raw) ? resolve10(raw) : resolve10(root, raw);
14487
+ if (!inside3(root, abs)) return null;
14488
+ const rel = relative6(root, abs).split(sep4).join("/");
14391
14489
  return rel && rel !== ".." ? rel : null;
14392
14490
  }
14393
14491
  function likelyConfig(path) {
@@ -14405,7 +14503,7 @@ function summarizeTraceRuns(opts) {
14405
14503
  for (const item of run) {
14406
14504
  const rel = normalRepoPath(opts.root, item.path);
14407
14505
  const isTracked = rel !== null && opts.tracked.has(rel);
14408
- const keyPath = rel !== null ? opts.tracked.has(rel) ? rel : null : resolve9(opts.root, item.path);
14506
+ const keyPath = rel !== null ? opts.tracked.has(rel) ? rel : null : resolve10(opts.root, item.path);
14409
14507
  if (keyPath === null) continue;
14410
14508
  const target = isTracked ? repo : external;
14411
14509
  const key2 = `${isTracked ? "repo" : "external"}\0${keyPath}`;
@@ -14484,22 +14582,22 @@ function materializeBase(base, cwd, dest) {
14484
14582
  }
14485
14583
  const hostModules = join21(cwd, "node_modules");
14486
14584
  const tracedModules = join21(dest, "node_modules");
14487
- if (existsSync14(hostModules) && !existsSync14(tracedModules)) {
14488
- symlinkSync2(resolve9(hostModules), tracedModules, "dir");
14585
+ if (existsSync15(hostModules) && !existsSync15(tracedModules)) {
14586
+ symlinkSync2(resolve10(hostModules), tracedModules, "dir");
14489
14587
  }
14490
14588
  }
14491
14589
  function traceFiles(prefix) {
14492
- const dir = resolve9(prefix, "..");
14590
+ const dir = resolve10(prefix, "..");
14493
14591
  const base = prefix.split(sep4).at(-1) ?? prefix;
14494
14592
  return readdirSync6(dir).filter((name) => name === base || name.startsWith(base + ".")).map((name) => join21(dir, name)).sort();
14495
14593
  }
14496
14594
  function rewriteTraceRoot(accesses, actualRoot) {
14497
14595
  return accesses.map((item) => {
14498
- const abs2 = isAbsolute11(item.path) ? resolve9(item.path) : resolve9(actualRoot, item.path);
14499
- if (!inside3(actualRoot, abs2)) return { ...item, path: abs2 };
14500
- const rel = relative6(actualRoot, abs2).split(sep4).join("/");
14596
+ const abs = isAbsolute11(item.path) ? resolve10(item.path) : resolve10(actualRoot, item.path);
14597
+ if (!inside3(actualRoot, abs)) return { ...item, path: abs };
14598
+ const rel = relative6(actualRoot, abs).split(sep4).join("/");
14501
14599
  try {
14502
- const real = resolve9(realpathSync8(abs2));
14600
+ const real = resolve10(realpathSync9(abs));
14503
14601
  if (!inside3(actualRoot, real)) return { ...item, path: real };
14504
14602
  } catch {
14505
14603
  }
@@ -14588,7 +14686,7 @@ function renderText2(report2) {
14588
14686
  for (const note of report2.notes) out3(`note: ${note}`);
14589
14687
  }
14590
14688
  function runTraceVerify(opts = {}) {
14591
- const cwd = resolve9(opts.cwd ?? process.cwd());
14689
+ const cwd = resolve10(opts.cwd ?? process.cwd());
14592
14690
  if (process.platform !== "linux") {
14593
14691
  process.stderr.write("tamperward trace-verify: Linux only in this release; unsupported platform \u2014 no parity is implied.\n");
14594
14692
  return 2;
@@ -14736,9 +14834,9 @@ var init_trace_verify = __esm({
14736
14834
  // src/cli/onboard.ts
14737
14835
  import { execFileSync as execFileSync12 } from "node:child_process";
14738
14836
  import { randomBytes as randomBytes2 } from "node:crypto";
14739
- import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync20, realpathSync as realpathSync9, renameSync as renameSync2, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
14837
+ import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync20, realpathSync as realpathSync10, renameSync as renameSync2, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
14740
14838
  import { tmpdir as tmpdir7 } from "node:os";
14741
- import { join as join22, relative as relative7, resolve as resolve10, sep as sep5 } from "node:path";
14839
+ import { join as join22, relative as relative7, resolve as resolve11, sep as sep5 } from "node:path";
14742
14840
  import { createInterface } from "node:readline";
14743
14841
  import { parseDocument } from "yaml";
14744
14842
  function readlineAsker(input, output) {
@@ -14747,9 +14845,9 @@ function readlineAsker(input, output) {
14747
14845
  let pending = null;
14748
14846
  rl.on("close", () => {
14749
14847
  closed = true;
14750
- const resolve15 = pending;
14848
+ const resolve16 = pending;
14751
14849
  pending = null;
14752
- resolve15?.(null);
14850
+ resolve16?.(null);
14753
14851
  });
14754
14852
  rl.on("SIGINT", () => rl.close());
14755
14853
  const ask = (question) => new Promise((resolvePromise) => {
@@ -14775,7 +14873,7 @@ function terminalText(text) {
14775
14873
  return stripControl(text).replace(/\s+/g, " ").trim();
14776
14874
  }
14777
14875
  function committableSetupPath(cwd, path) {
14778
- const rel = relative7(cwd, resolve10(cwd, path));
14876
+ const rel = relative7(cwd, resolve11(cwd, path));
14779
14877
  if (!rel || rel === ".." || rel.startsWith(".." + sep5)) return false;
14780
14878
  return rel !== ".git" && !rel.startsWith(".git" + sep5);
14781
14879
  }
@@ -14844,7 +14942,7 @@ function writeVerifyCommand(cwd, command) {
14844
14942
  }
14845
14943
  }
14846
14944
  async function runOnboard(opts, io = {}) {
14847
- const requestedCwd = resolve10(opts.cwd ?? process.cwd());
14945
+ const requestedCwd = resolve11(opts.cwd ?? process.cwd());
14848
14946
  const out3 = io.out ?? ((line) => void process.stdout.write(line + "\n"));
14849
14947
  const rawErr = (line) => void process.stderr.write(terminalText(line) + "\n");
14850
14948
  const platform = io.platform ?? process.platform;
@@ -14880,10 +14978,10 @@ async function runOnboard(opts, io = {}) {
14880
14978
  fail(requestedCwd + " is not inside a Git repository. Run `git init` first.");
14881
14979
  return 2;
14882
14980
  }
14883
- const cwd = resolve10(rootText);
14981
+ const cwd = resolve11(rootText);
14884
14982
  let sameRoot = cwd === requestedCwd;
14885
14983
  try {
14886
- sameRoot = realpathSync9(cwd) === realpathSync9(requestedCwd);
14984
+ sameRoot = realpathSync10(cwd) === realpathSync10(requestedCwd);
14887
14985
  } catch {
14888
14986
  }
14889
14987
  if (!sameRoot) {
@@ -15216,8 +15314,8 @@ function runDemo(cwd, head, runners, out3) {
15216
15314
  let worktreeRoot;
15217
15315
  let realTarget;
15218
15316
  try {
15219
- worktreeRoot = realpathSync9(wt);
15220
- realTarget = realpathSync9(targetPath);
15317
+ worktreeRoot = realpathSync10(wt);
15318
+ realTarget = realpathSync10(targetPath);
15221
15319
  } catch {
15222
15320
  out3("Demo skipped: the selected test could not be resolved safely inside the disposable worktree.");
15223
15321
  return;
@@ -15291,7 +15389,7 @@ var init_onboard = __esm({
15291
15389
  });
15292
15390
 
15293
15391
  // src/research/adapter.ts
15294
- import { isAbsolute as isAbsolute12, resolve as resolve11 } from "node:path";
15392
+ import { isAbsolute as isAbsolute12, resolve as resolve12 } from "node:path";
15295
15393
  function taskEnv(task) {
15296
15394
  return {
15297
15395
  TAMPERWARD_RESEARCH_TASK: task.id,
@@ -15307,7 +15405,7 @@ function normalizeCommandArgv(argv, cwd = process.cwd()) {
15307
15405
  throw new ResearchError('the command adapter needs an agent command after "--"');
15308
15406
  }
15309
15407
  const head = argv[0];
15310
- return head.includes("/") && !isAbsolute12(head) ? [resolve11(cwd, head), ...argv.slice(1)] : [...argv];
15408
+ return head.includes("/") && !isAbsolute12(head) ? [resolve12(cwd, head), ...argv.slice(1)] : [...argv];
15311
15409
  }
15312
15410
  function commandAdapter(argv, cwd = process.cwd()) {
15313
15411
  const anchored = normalizeCommandArgv(argv, cwd);
@@ -15408,7 +15506,7 @@ var init_capture = __esm({
15408
15506
  // src/research/manifest.ts
15409
15507
  import { createHash as createHash12 } from "node:crypto";
15410
15508
  import { readFileSync as readFileSync21 } from "node:fs";
15411
- import { dirname as dirname9, resolve as resolve12 } from "node:path";
15509
+ import { dirname as dirname9, resolve as resolve13 } from "node:path";
15412
15510
  function taskFrom(raw, index, manifestDir) {
15413
15511
  const where = `task #${index + 1}`;
15414
15512
  if (!isRecord(raw)) throw new ResearchError(`task manifest: ${where} is not an object`);
@@ -15431,40 +15529,40 @@ function taskFrom(raw, index, manifestDir) {
15431
15529
  const isUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(repo) || /^[^/]+@[^:]+:/.test(repo);
15432
15530
  return {
15433
15531
  id,
15434
- repo: isUrl ? repo : resolve12(manifestDir, repo),
15532
+ repo: isUrl ? repo : resolve13(manifestDir, repo),
15435
15533
  base: base ?? "HEAD",
15436
15534
  prompt,
15437
15535
  verify: { command: verify.command, ...budget !== void 0 ? { budget } : {} }
15438
15536
  };
15439
15537
  }
15440
15538
  function readManifest(path) {
15441
- const abs2 = resolve12(path);
15539
+ const abs = resolve13(path);
15442
15540
  let bytes;
15443
15541
  try {
15444
- bytes = readFileSync21(abs2);
15542
+ bytes = readFileSync21(abs);
15445
15543
  } catch (e) {
15446
- throw new ResearchError(`cannot read task manifest ${abs2}: ${e instanceof Error ? e.message : String(e)}`);
15544
+ throw new ResearchError(`cannot read task manifest ${abs}: ${e instanceof Error ? e.message : String(e)}`);
15447
15545
  }
15448
15546
  let raw;
15449
15547
  try {
15450
15548
  raw = JSON.parse(bytes.toString("utf8"));
15451
15549
  } catch {
15452
- throw new ResearchError(`task manifest ${abs2} is not valid JSON`);
15550
+ throw new ResearchError(`task manifest ${abs} is not valid JSON`);
15453
15551
  }
15454
- if (!isRecord(raw)) throw new ResearchError(`task manifest ${abs2}: top level must be an object`);
15552
+ if (!isRecord(raw)) throw new ResearchError(`task manifest ${abs}: top level must be an object`);
15455
15553
  if (raw.version !== MANIFEST_VERSION) {
15456
- throw new ResearchError(`task manifest ${abs2}: version must be ${MANIFEST_VERSION} (got ${JSON.stringify(raw.version)})`);
15554
+ throw new ResearchError(`task manifest ${abs}: version must be ${MANIFEST_VERSION} (got ${JSON.stringify(raw.version)})`);
15457
15555
  }
15458
15556
  if (!Array.isArray(raw.tasks) || raw.tasks.length === 0) {
15459
- throw new ResearchError(`task manifest ${abs2}: no tasks (tasks must be a non-empty array)`);
15557
+ throw new ResearchError(`task manifest ${abs}: no tasks (tasks must be a non-empty array)`);
15460
15558
  }
15461
- const tasks = raw.tasks.map((t, i) => taskFrom(t, i, dirname9(abs2)));
15559
+ const tasks = raw.tasks.map((t, i) => taskFrom(t, i, dirname9(abs)));
15462
15560
  const seen = /* @__PURE__ */ new Set();
15463
15561
  for (const t of tasks) {
15464
- if (seen.has(t.id)) throw new ResearchError(`task manifest ${abs2}: duplicate task id "${t.id}"`);
15562
+ if (seen.has(t.id)) throw new ResearchError(`task manifest ${abs}: duplicate task id "${t.id}"`);
15465
15563
  seen.add(t.id);
15466
15564
  }
15467
- return { path: abs2, sha256: createHash12("sha256").update(bytes).digest("hex"), tasks };
15565
+ return { path: abs, sha256: createHash12("sha256").update(bytes).digest("hex"), tasks };
15468
15566
  }
15469
15567
  var MANIFEST_VERSION;
15470
15568
  var init_manifest = __esm({
@@ -15653,8 +15751,8 @@ var init_record = __esm({
15653
15751
 
15654
15752
  // src/research/run.ts
15655
15753
  import { execFileSync as execFileSync13 } from "node:child_process";
15656
- import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync22, renameSync as renameSync3, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "node:fs";
15657
- import { join as join23, resolve as resolve13 } from "node:path";
15754
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync22, renameSync as renameSync3, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "node:fs";
15755
+ import { join as join23, resolve as resolve14 } from "node:path";
15658
15756
  function git6(args, cwd) {
15659
15757
  return execFileSync13("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
15660
15758
  }
@@ -15717,9 +15815,9 @@ function freshWorkspace(ledger, task, pair, arm, sourceBase) {
15717
15815
  git6(["config", "user.email", "research@tamperward.invalid"], ws2);
15718
15816
  const requestedBase = sourceBase ?? task.base;
15719
15817
  const candidates = sourceBase !== void 0 ? [sourceBase] : task.base === "HEAD" ? ["HEAD"] : [task.base, `origin/${task.base}`];
15720
- for (const rev of candidates) {
15818
+ for (const rev2 of candidates) {
15721
15819
  try {
15722
- git6(["checkout", "-q", "--detach", rev], ws2);
15820
+ git6(["checkout", "-q", "--detach", rev2], ws2);
15723
15821
  return ws2;
15724
15822
  } catch {
15725
15823
  }
@@ -15953,7 +16051,7 @@ function runResearch(opts) {
15953
16051
  err2("tamperward research: the gated arm cannot start here (see `tamperward doctor`); run as a non-root user on Linux.");
15954
16052
  return 2;
15955
16053
  }
15956
- const ledger = resolve13(opts.out);
16054
+ const ledger = resolve14(opts.out);
15957
16055
  const pairs = opts.pairs ?? 1;
15958
16056
  mkdirSync8(join23(ledger, "pairs"), { recursive: true });
15959
16057
  for (const task of tasks) {
@@ -15962,7 +16060,7 @@ function runResearch(opts) {
15962
16060
  try {
15963
16061
  for (let pair = 1; pair <= pairs; pair++) {
15964
16062
  const path = pairRecordPath(ledger, task.id, pair);
15965
- if (!existsSync15(path)) continue;
16063
+ if (!existsSync16(path)) continue;
15966
16064
  const existing = resumableRecord(path, {
15967
16065
  task: task.id,
15968
16066
  pair,
@@ -16088,9 +16186,9 @@ var init_run2 = __esm({
16088
16186
 
16089
16187
  // src/research/summarize.ts
16090
16188
  import { readdirSync as readdirSync7, readFileSync as readFileSync23 } from "node:fs";
16091
- import { join as join24, resolve as resolve14 } from "node:path";
16189
+ import { join as join24, resolve as resolve15 } from "node:path";
16092
16190
  function readLedger2(dir) {
16093
- const pairsDir = join24(resolve14(dir), "pairs");
16191
+ const pairsDir = join24(resolve15(dir), "pairs");
16094
16192
  let names;
16095
16193
  try {
16096
16194
  names = readdirSync7(pairsDir).filter((n) => n.endsWith(".json")).sort();