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/README.md +1 -1
- package/dist/cli/index.js +386 -288
- package/package.json +1 -1
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((
|
|
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
|
-
|
|
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(
|
|
606
|
+
function readRegular(abs, st) {
|
|
607
607
|
let fd;
|
|
608
608
|
try {
|
|
609
|
-
fd = openSync(
|
|
609
|
+
fd = openSync(abs, O_READ);
|
|
610
610
|
} catch (e) {
|
|
611
|
-
if (errCode(e) === "ELOOP") return inspectOnce(
|
|
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(
|
|
632
|
+
function classify(abs, st) {
|
|
633
633
|
if (st.isSymbolicLink()) {
|
|
634
634
|
try {
|
|
635
|
-
const target = readlinkSync(
|
|
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(
|
|
644
|
+
return readRegular(abs, st);
|
|
645
645
|
}
|
|
646
|
-
function inspectOnce(
|
|
646
|
+
function inspectOnce(abs) {
|
|
647
647
|
let st;
|
|
648
648
|
try {
|
|
649
|
-
st = lstatSync2(
|
|
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(
|
|
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(
|
|
658
|
+
function inspectPath(abs) {
|
|
659
659
|
let st;
|
|
660
660
|
try {
|
|
661
|
-
st = lstatSync2(
|
|
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(
|
|
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(
|
|
672
|
+
function inspectResolved(abs) {
|
|
673
673
|
let real;
|
|
674
674
|
try {
|
|
675
|
-
real = realpathSync2(
|
|
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/
|
|
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
|
|
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(
|
|
1094
|
+
function blobAt(rev2, path, cwd) {
|
|
1051
1095
|
try {
|
|
1052
|
-
return git(["show", `${
|
|
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(
|
|
1067
|
-
if (
|
|
1068
|
-
return
|
|
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(
|
|
1078
|
-
return blobAt(
|
|
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(
|
|
1106
|
-
const raw = git([...DIFF_ARGS,
|
|
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(
|
|
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
|
|
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
|
|
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("..") && !
|
|
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 =
|
|
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
|
|
1270
|
-
return synthFileChange(relForDisplay(abs(fp
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
2574
|
-
if (!ctx?.cwd ||
|
|
2616
|
+
function revIsHead(rev2, ctx) {
|
|
2617
|
+
if (!ctx?.cwd || rev2.startsWith("-")) return null;
|
|
2575
2618
|
const parse = (r) => {
|
|
2576
2619
|
try {
|
|
2577
|
-
return
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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,
|
|
2649
|
-
const obj =
|
|
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(
|
|
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
|
|
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 =
|
|
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,
|
|
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,
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
4051
|
-
import { isAbsolute as
|
|
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 &&
|
|
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 =
|
|
5078
|
-
const
|
|
5079
|
-
if (pathy &&
|
|
5080
|
-
p = canonicalPath(
|
|
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
|
|
5354
|
-
import { isAbsolute as
|
|
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 (
|
|
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 || !
|
|
5839
|
+
if (!ctx?.cwd || !isAbsolute6(path)) return path;
|
|
5797
5840
|
const rel = relative2(ctx.cwd, path);
|
|
5798
|
-
return rel && !rel.startsWith("..") && !
|
|
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 && (
|
|
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
|
|
6945
|
-
if (!
|
|
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
|
-
|
|
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
|
|
7560
|
-
import { isAbsolute as
|
|
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() ||
|
|
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 (!
|
|
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(
|
|
7725
|
-
const src = fileAt(
|
|
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 ${
|
|
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
|
|
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 (!
|
|
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
|
|
8135
|
-
if (!isGitRepo(
|
|
8136
|
-
process.stderr.write(`tamperward: ${
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
8296
|
-
import {
|
|
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
|
-
|
|
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,
|
|
8419
|
+
function snapshotProtected(cwd, policy, prev, cache3) {
|
|
8343
8420
|
void prev;
|
|
8344
|
-
|
|
8345
|
-
|
|
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
|
|
8435
|
+
const abs = rel ? join11(cwd, rel) : cwd;
|
|
8358
8436
|
let names;
|
|
8359
8437
|
try {
|
|
8360
|
-
names = readdirSync(
|
|
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 || !
|
|
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(
|
|
8455
|
-
const e = inspectPath(
|
|
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
|
|
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(
|
|
8974
|
-
snapshotCache =
|
|
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
|
|
9105
|
-
const blob = fileAt(
|
|
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
|
|
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 (!
|
|
9333
|
+
if (!existsSync9(log)) return none;
|
|
9254
9334
|
let offset = 0;
|
|
9255
|
-
if (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
|
|
9325
|
-
if (!isGitRepo(
|
|
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
|
|
9553
|
+
const abs = join13(cwd, rel);
|
|
9472
9554
|
let st;
|
|
9473
9555
|
try {
|
|
9474
|
-
st = lstatSync5(
|
|
9556
|
+
st = lstatSync5(abs, { bigint: true });
|
|
9475
9557
|
} catch {
|
|
9476
|
-
this.map.delete(
|
|
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(
|
|
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(
|
|
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(
|
|
9578
|
+
after = lstatSync5(abs, { bigint: true });
|
|
9497
9579
|
} catch {
|
|
9498
|
-
this.map.delete(
|
|
9580
|
+
this.map.delete(abs);
|
|
9499
9581
|
return entry;
|
|
9500
9582
|
}
|
|
9501
9583
|
if (!after.isFile() || keyOf(after) !== key2) {
|
|
9502
|
-
this.map.delete(
|
|
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(
|
|
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
|
|
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((
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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:
|
|
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((
|
|
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
|
-
|
|
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(
|
|
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((
|
|
9719
|
-
if (closed) return
|
|
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
|
-
|
|
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) || !
|
|
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
|
|
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(
|
|
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
|
|
9938
|
-
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute8, join as join14, relative as relative3, resolve as
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
10202
|
+
if (!existsSync11(join14(cwd, "node_modules"))) return null;
|
|
10119
10203
|
let existing = "";
|
|
10120
|
-
if (
|
|
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:
|
|
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 (
|
|
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:
|
|
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
|
|
10334
|
+
if (out3) return resolve4(cwd, out3);
|
|
10251
10335
|
} catch {
|
|
10252
10336
|
}
|
|
10253
|
-
return
|
|
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 (
|
|
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) =>
|
|
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 (
|
|
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 =
|
|
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) =>
|
|
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 (!
|
|
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
|
|
10499
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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" + (
|
|
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:
|
|
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 :
|
|
11031
|
+
const path = isAbsolute9(token) ? token : resolve5(cwd, token);
|
|
10935
11032
|
try {
|
|
10936
|
-
return
|
|
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 =
|
|
11039
|
+
const candidate = resolve5(entry, token);
|
|
10943
11040
|
try {
|
|
10944
|
-
if (
|
|
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 :
|
|
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
|
|
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
|
|
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 (!
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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) &&
|
|
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
|
-
|
|
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:
|
|
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 !==
|
|
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
|
|
11451
|
-
import { delimiter as delimiter2, dirname as dirname6, join as join17, resolve as
|
|
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 =
|
|
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
|
|
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 (
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
12042
|
+
const rev2 = git2(["rev-parse", "--verify", `${base}^{commit}`], cwd).trim();
|
|
11946
12043
|
try {
|
|
11947
|
-
return git2(["merge-base",
|
|
12044
|
+
return git2(["merge-base", rev2, "HEAD"], cwd).trim();
|
|
11948
12045
|
} catch {
|
|
11949
|
-
return
|
|
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(
|
|
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,
|
|
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,
|
|
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 !
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
[
|
|
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 =
|
|
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("/") ?
|
|
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 =
|
|
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
|
|
13720
|
-
import { join as join20, resolve as
|
|
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 (!
|
|
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 =
|
|
13799
|
-
if (!
|
|
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 =
|
|
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 =
|
|
14170
|
-
if (!
|
|
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 =
|
|
14190
|
-
if (!
|
|
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
|
|
14448
|
+
existsSync as existsSync15,
|
|
14351
14449
|
mkdtempSync as mkdtempSync5,
|
|
14352
14450
|
readFileSync as readFileSync19,
|
|
14353
14451
|
readdirSync as readdirSync6,
|
|
14354
|
-
realpathSync as
|
|
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
|
|
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(
|
|
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
|
|
14389
|
-
if (!inside3(root,
|
|
14390
|
-
const rel = relative6(root,
|
|
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 :
|
|
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 (
|
|
14488
|
-
symlinkSync2(
|
|
14585
|
+
if (existsSync15(hostModules) && !existsSync15(tracedModules)) {
|
|
14586
|
+
symlinkSync2(resolve10(hostModules), tracedModules, "dir");
|
|
14489
14587
|
}
|
|
14490
14588
|
}
|
|
14491
14589
|
function traceFiles(prefix) {
|
|
14492
|
-
const dir =
|
|
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
|
|
14499
|
-
if (!inside3(actualRoot,
|
|
14500
|
-
const rel = relative6(actualRoot,
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
14848
|
+
const resolve16 = pending;
|
|
14751
14849
|
pending = null;
|
|
14752
|
-
|
|
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,
|
|
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 =
|
|
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 =
|
|
14981
|
+
const cwd = resolve11(rootText);
|
|
14884
14982
|
let sameRoot = cwd === requestedCwd;
|
|
14885
14983
|
try {
|
|
14886
|
-
sameRoot =
|
|
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 =
|
|
15220
|
-
realTarget =
|
|
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
|
|
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) ? [
|
|
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
|
|
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 :
|
|
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
|
|
15539
|
+
const abs = resolve13(path);
|
|
15442
15540
|
let bytes;
|
|
15443
15541
|
try {
|
|
15444
|
-
bytes = readFileSync21(
|
|
15542
|
+
bytes = readFileSync21(abs);
|
|
15445
15543
|
} catch (e) {
|
|
15446
|
-
throw new ResearchError(`cannot read task manifest ${
|
|
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 ${
|
|
15550
|
+
throw new ResearchError(`task manifest ${abs} is not valid JSON`);
|
|
15453
15551
|
}
|
|
15454
|
-
if (!isRecord(raw)) throw new ResearchError(`task manifest ${
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
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 ${
|
|
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:
|
|
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
|
|
15657
|
-
import { join as join23, resolve as
|
|
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
|
|
15818
|
+
for (const rev2 of candidates) {
|
|
15721
15819
|
try {
|
|
15722
|
-
git6(["checkout", "-q", "--detach",
|
|
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 =
|
|
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 (!
|
|
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
|
|
16189
|
+
import { join as join24, resolve as resolve15 } from "node:path";
|
|
16092
16190
|
function readLedger2(dir) {
|
|
16093
|
-
const pairsDir = join24(
|
|
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();
|