tamperward 2.26.0 → 2.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -211,7 +211,7 @@ function forwardedEnv(env) {
211
211
  return out3;
212
212
  }
213
213
  function exchange(paths, req2, timeoutMs) {
214
- return new Promise((resolve16) => {
214
+ return new Promise((resolve17) => {
215
215
  let done = false;
216
216
  let handedOff = false;
217
217
  let accepted = false;
@@ -222,7 +222,7 @@ function exchange(paths, req2, timeoutMs) {
222
222
  done = true;
223
223
  clearTimeout(timer);
224
224
  sock.destroy();
225
- resolve16(value);
225
+ resolve17(value);
226
226
  };
227
227
  const sock = createConnection(paths.socket);
228
228
  const timer = setTimeout(() => finish(handedOff ? acceptedFailure("timeout") : null), timeoutMs);
@@ -298,6 +298,7 @@ var init_hook_client = __esm({
298
298
  MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
299
299
  FORWARDED_ENV = [
300
300
  "TAMPERWARD_DENYLOG",
301
+ "TAMPERWARD_AUDIT_LOG",
301
302
  "TAMPERWARD_FSEVENTS",
302
303
  "TAMPERWARD_TRANSIENT",
303
304
  "CLAUDE_CONFIG_DIR",
@@ -1090,7 +1091,7 @@ var init_trusted = __esm({
1090
1091
 
1091
1092
  // src/repo-context.ts
1092
1093
  import { execFileSync as execFileSync2 } from "node:child_process";
1093
- import { accessSync, constants as constants2, existsSync as existsSync2, statSync } from "node:fs";
1094
+ import { accessSync, constants as constants2, existsSync as existsSync2, realpathSync as realpathSync3, statSync } from "node:fs";
1094
1095
  import { isAbsolute as isAbsolute3, resolve } from "node:path";
1095
1096
  function rev(cwd, flag) {
1096
1097
  try {
@@ -1123,6 +1124,27 @@ function repoContext(cwd) {
1123
1124
  function repoRoot(cwd) {
1124
1125
  return repoContext(cwd)?.root ?? cwd;
1125
1126
  }
1127
+ function validateClaimAgainstRoot(claimedCwd, trustedRoot, base) {
1128
+ if (claimedCwd === void 0) return { ok: true, trustedRoot };
1129
+ if (typeof claimedCwd !== "string" || claimedCwd.trim() === "") return { ok: false, rejected: "malformed cwd claim" };
1130
+ const abs = isAbsolute3(claimedCwd) ? claimedCwd : resolve(base, claimedCwd);
1131
+ let realClaim;
1132
+ try {
1133
+ realClaim = realpathSync3(abs);
1134
+ } catch (e) {
1135
+ return { ok: false, rejected: `cwd claim path cannot be resolved (${abs}: ${e instanceof Error ? e.message : String(e)})` };
1136
+ }
1137
+ let realTrusted;
1138
+ try {
1139
+ realTrusted = realpathSync3(trustedRoot);
1140
+ } catch {
1141
+ realTrusted = trustedRoot;
1142
+ }
1143
+ const ctx = repoContext(realClaim);
1144
+ if (!ctx) return { ok: false, rejected: `cwd claim resolves to no repository (${realClaim})` };
1145
+ if (ctx.root !== realTrusted) return { ok: false, rejected: `cwd claim resolves to a different repository (${ctx.root} != trusted ${realTrusted})` };
1146
+ return { ok: true, trustedRoot: realTrusted };
1147
+ }
1126
1148
  function outsideRepository(cwd) {
1127
1149
  const key2 = resolve(cwd);
1128
1150
  let isDir;
@@ -3517,15 +3539,15 @@ function buildAliasMap(sf) {
3517
3539
  ts.forEachChild(node, collect2);
3518
3540
  };
3519
3541
  collect2(sf);
3520
- const resolve16 = (name, seen) => {
3542
+ const resolve17 = (name, seen) => {
3521
3543
  if (seen.has(name)) return null;
3522
3544
  seen.add(name);
3523
3545
  const t = decls.get(name);
3524
- return t ? kindOfType(t, (n) => resolve16(n, seen)) : null;
3546
+ return t ? kindOfType(t, (n) => resolve17(n, seen)) : null;
3525
3547
  };
3526
3548
  const resolved = /* @__PURE__ */ new Map();
3527
3549
  for (const name of decls.keys()) {
3528
- const k = resolve16(name, /* @__PURE__ */ new Set());
3550
+ const k = resolve17(name, /* @__PURE__ */ new Set());
3529
3551
  if (k) resolved.set(name, k);
3530
3552
  }
3531
3553
  return resolved;
@@ -4912,8 +4934,8 @@ function numericOf(e) {
4912
4934
  }
4913
4935
  return void 0;
4914
4936
  }
4915
- function metricsOf(expr, resolve16) {
4916
- const obj = resolve16(expr);
4937
+ function metricsOf(expr, resolve17) {
4938
+ const obj = resolve17(expr);
4917
4939
  if (!ts.isObjectLiteralExpression(obj)) return void 0;
4918
4940
  const out3 = { values: {}, opaque: false };
4919
4941
  let any = false;
@@ -4927,7 +4949,7 @@ function metricsOf(expr, resolve16) {
4927
4949
  const k = keyName(p.name);
4928
4950
  if (k === null || !isMetric(k)) continue;
4929
4951
  any = true;
4930
- const n = numericOf(resolve16(p.initializer));
4952
+ const n = numericOf(resolve17(p.initializer));
4931
4953
  if (n === void 0) out3.opaque = true;
4932
4954
  else out3.values[k] = n;
4933
4955
  }
@@ -4974,17 +4996,17 @@ function parseThresholds(src) {
4974
4996
  try {
4975
4997
  const sf = sourceOf(src);
4976
4998
  if (!sf) return res;
4977
- const resolve16 = resolverFor(sf);
4999
+ const resolve17 = resolverFor(sf);
4978
5000
  const visit = (node) => {
4979
5001
  if (ts.isPropertyAssignment(node)) {
4980
5002
  const key2 = keyName(node.name);
4981
5003
  if (key2 === "coverageThreshold" || key2 === "thresholds" && underKey(node, "coverage")) {
4982
5004
  res.present = true;
4983
- const init = resolve16(node.initializer);
5005
+ const init = resolve17(node.initializer);
4984
5006
  if (!ts.isObjectLiteralExpression(init)) {
4985
5007
  res.opaque = true;
4986
5008
  } else {
4987
- const flat = metricsOf(init, resolve16);
5009
+ const flat = metricsOf(init, resolve17);
4988
5010
  if (flat) res.global = merge(res.global, flat);
4989
5011
  for (const p of init.properties) {
4990
5012
  if (ts.isSpreadAssignment(p)) {
@@ -4998,7 +5020,7 @@ function parseThresholds(src) {
4998
5020
  res.global = merge(res.global, { values: { branches: 100, functions: 100, lines: 100, statements: 100 }, opaque: false });
4999
5021
  continue;
5000
5022
  }
5001
- const m = metricsOf(p.initializer, resolve16);
5023
+ const m = metricsOf(p.initializer, resolve17);
5002
5024
  if (!m) continue;
5003
5025
  if (k === "global") res.global = merge(res.global, m);
5004
5026
  else res.paths.set(norm(k), m);
@@ -5032,18 +5054,18 @@ function parseLists(src) {
5032
5054
  try {
5033
5055
  const sf = sourceOf(src);
5034
5056
  if (!sf) return res;
5035
- const resolve16 = resolverFor(sf);
5057
+ const resolve17 = resolverFor(sf);
5036
5058
  const visit = (node) => {
5037
5059
  if (ts.isPropertyAssignment(node)) {
5038
5060
  const k = keyName(node.name);
5039
5061
  const vitestCoverage = ownerKey(node) === "coverage";
5040
5062
  if (k === "collectCoverageFrom" || k === "include" && vitestCoverage) {
5041
- const { items, opaque } = literals(resolve16(node.initializer));
5063
+ const { items, opaque } = literals(resolve17(node.initializer));
5042
5064
  res.collectFrom = items;
5043
5065
  res.collectKey = k === "include" ? "coverage.include" : k;
5044
5066
  if (opaque) res.opaqueCollect = true;
5045
5067
  } else if (k === "coveragePathIgnorePatterns" || k === "exclude" && vitestCoverage) {
5046
- const { items, opaque } = literals(resolve16(node.initializer));
5068
+ const { items, opaque } = literals(resolve17(node.initializer));
5047
5069
  (k === "exclude" ? res.ignore : res.ignoreRe).push(...items);
5048
5070
  if (opaque) res.opaqueIgnore = true;
5049
5071
  }
@@ -11808,15 +11830,251 @@ var init_fs_events = __esm({
11808
11830
  }
11809
11831
  });
11810
11832
 
11833
+ // src/cli/audit.ts
11834
+ import { createHash as createHash5, randomBytes } from "node:crypto";
11835
+ import { appendFileSync as appendFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync10 } from "node:fs";
11836
+ import { dirname as dirname4, isAbsolute as isAbsolute8, join as join13, resolve as resolve4 } from "node:path";
11837
+ function sessionHash(sessionId) {
11838
+ if (!sessionId) return void 0;
11839
+ return "sha256:" + createHash5("sha256").update(sessionId).digest("hex").slice(0, 24);
11840
+ }
11841
+ function eventId() {
11842
+ return "sha256:" + createHash5("sha256").update(randomBytes(32)).digest("hex").slice(0, 32);
11843
+ }
11844
+ function configuredAuditPath(cwd, env = process.env) {
11845
+ const value = env[AUDIT_LOG_ENV];
11846
+ if (!value) return null;
11847
+ if (value === "auto") {
11848
+ const ctx = repoContext(cwd);
11849
+ return ctx ? join13(ctx.gitDir, "tamperward", "audit.jsonl") : null;
11850
+ }
11851
+ return isAbsolute8(value) ? value : resolve4(cwd, value);
11852
+ }
11853
+ function defaultAuditPath(cwd) {
11854
+ const ctx = repoContext(cwd);
11855
+ return ctx ? join13(ctx.gitDir, "tamperward", "audit.jsonl") : null;
11856
+ }
11857
+ function recordAuditFindings(findings, context) {
11858
+ if (findings.length === 0) return;
11859
+ const path = configuredAuditPath(context.cwd);
11860
+ if (!path) return;
11861
+ try {
11862
+ mkdirSync5(dirname4(path), { recursive: true });
11863
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
11864
+ const session = sessionHash(context.sessionId);
11865
+ const lines = findings.map((finding) => {
11866
+ const severity = finding.severity === "block" ? "block" : "warn";
11867
+ const event = {
11868
+ schema_version: AUDIT_SCHEMA_VERSION,
11869
+ id: eventId(),
11870
+ timestamp,
11871
+ surface: context.surface,
11872
+ agent: "claude-code",
11873
+ rule: finding.rule,
11874
+ severity,
11875
+ decision: severity === "block" ? "deny" : "warn",
11876
+ ...session ? { session } : {}
11877
+ };
11878
+ return JSON.stringify(event);
11879
+ });
11880
+ appendFileSync4(path, lines.join("\n") + "\n");
11881
+ } catch {
11882
+ }
11883
+ }
11884
+ function recordAuditSignal(rule, severity, context) {
11885
+ const finding = {
11886
+ rule,
11887
+ severity,
11888
+ message: "",
11889
+ evidence: "",
11890
+ remediation: "",
11891
+ signoff: { required: severity === "block", command: "" }
11892
+ };
11893
+ recordAuditFindings([finding], context);
11894
+ }
11895
+ function isRecord3(v) {
11896
+ return typeof v === "object" && v !== null && !Array.isArray(v);
11897
+ }
11898
+ function parseAuditEvent(value, line = 0) {
11899
+ const where = line > 0 ? `audit line ${line}` : "audit event";
11900
+ if (!isRecord3(value)) throw new Error(`${where} must be a JSON object`);
11901
+ const unknown = Object.keys(value).filter((key2) => !ALLOWED_KEYS.has(key2));
11902
+ if (unknown.length) throw new Error(`${where} contains unsupported field "${unknown[0]}"`);
11903
+ if (value.schema_version !== AUDIT_SCHEMA_VERSION) {
11904
+ throw new Error(`${where} has unsupported schema_version ${String(value.schema_version)}`);
11905
+ }
11906
+ if (typeof value.id !== "string" || !/^sha256:[0-9a-f]{32}$/.test(value.id)) {
11907
+ throw new Error(`${where} has an invalid id`);
11908
+ }
11909
+ if (typeof value.timestamp !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value.timestamp) || !Number.isFinite(Date.parse(value.timestamp))) {
11910
+ throw new Error(`${where} has an invalid timestamp`);
11911
+ }
11912
+ if (value.surface !== "pretooluse" && value.surface !== "stop") {
11913
+ throw new Error(`${where} has an invalid surface`);
11914
+ }
11915
+ if (typeof value.agent !== "string" || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(value.agent)) {
11916
+ throw new Error(`${where} has an invalid agent`);
11917
+ }
11918
+ if (typeof value.rule !== "string" || !/^[a-z0-9][a-z0-9-]{0,79}$/.test(value.rule)) {
11919
+ throw new Error(`${where} has an invalid rule`);
11920
+ }
11921
+ if (value.severity !== "block" && value.severity !== "warn") {
11922
+ throw new Error(`${where} has an invalid severity`);
11923
+ }
11924
+ if (value.decision !== "deny" && value.decision !== "warn") {
11925
+ throw new Error(`${where} has an invalid decision`);
11926
+ }
11927
+ if (value.severity === "block" && value.decision !== "deny" || value.severity === "warn" && value.decision !== "warn") {
11928
+ throw new Error(`${where} has inconsistent severity and decision`);
11929
+ }
11930
+ if (value.session !== void 0 && (typeof value.session !== "string" || !/^sha256:[0-9a-f]{24}$/.test(value.session))) {
11931
+ throw new Error(`${where} has an invalid session`);
11932
+ }
11933
+ return {
11934
+ schema_version: AUDIT_SCHEMA_VERSION,
11935
+ id: value.id,
11936
+ timestamp: value.timestamp,
11937
+ surface: value.surface,
11938
+ agent: value.agent,
11939
+ rule: value.rule,
11940
+ severity: value.severity,
11941
+ decision: value.decision,
11942
+ ...typeof value.session === "string" ? { session: value.session } : {}
11943
+ };
11944
+ }
11945
+ function parseAuditJsonl(raw) {
11946
+ const events = [];
11947
+ raw.split(/\r?\n/).forEach((line, index) => {
11948
+ if (!line.trim()) return;
11949
+ let value;
11950
+ try {
11951
+ value = JSON.parse(line);
11952
+ } catch {
11953
+ throw new Error(`audit line ${index + 1} is not valid JSON`);
11954
+ }
11955
+ events.push(parseAuditEvent(value, index + 1));
11956
+ });
11957
+ return events;
11958
+ }
11959
+ function parseSince(value, nowMs = Date.now()) {
11960
+ const relative8 = /^(\d+)(m|h|d)$/.exec(value);
11961
+ if (relative8) {
11962
+ const count = Number(relative8[1]);
11963
+ const unit = relative8[2] === "m" ? 6e4 : relative8[2] === "h" ? 36e5 : 864e5;
11964
+ if (!Number.isSafeInteger(count) || count <= 0) throw new Error(`invalid --since value "${value}"`);
11965
+ return nowMs - count * unit;
11966
+ }
11967
+ const parsed = Date.parse(value);
11968
+ if (!Number.isFinite(parsed)) {
11969
+ throw new Error(`invalid --since value "${value}" (use e.g. 30d, 12h, 90m, or an ISO timestamp)`);
11970
+ }
11971
+ return parsed;
11972
+ }
11973
+ function summarizeAudit(events) {
11974
+ const ordered = [...events].sort((a, b) => a.timestamp.localeCompare(b.timestamp) || a.id.localeCompare(b.id));
11975
+ const rules = /* @__PURE__ */ new Map();
11976
+ const surfaces = /* @__PURE__ */ new Map();
11977
+ const sessions = /* @__PURE__ */ new Set();
11978
+ for (const event of ordered) {
11979
+ const bucket = rules.get(event.rule) ?? { events: 0, blocked: 0, warnings: 0 };
11980
+ bucket.events++;
11981
+ if (event.severity === "block") bucket.blocked++;
11982
+ else bucket.warnings++;
11983
+ rules.set(event.rule, bucket);
11984
+ surfaces.set(event.surface, (surfaces.get(event.surface) ?? 0) + 1);
11985
+ if (event.session) sessions.add(event.session);
11986
+ }
11987
+ const by_rule = [...rules.entries()].map(([rule, counts]) => ({ rule, ...counts })).sort((a, b) => b.events - a.events || a.rule.localeCompare(b.rule));
11988
+ const by_surface = [...surfaces.entries()].map(([surface, count]) => ({ surface, events: count })).sort((a, b) => b.events - a.events || a.surface.localeCompare(b.surface));
11989
+ return {
11990
+ schema_version: AUDIT_SCHEMA_VERSION,
11991
+ events: ordered.length,
11992
+ blocked: ordered.filter((event) => event.severity === "block").length,
11993
+ warnings: ordered.filter((event) => event.severity === "warn").length,
11994
+ sessions: sessions.size,
11995
+ first_event: ordered[0]?.timestamp ?? null,
11996
+ last_event: ordered.at(-1)?.timestamp ?? null,
11997
+ by_rule,
11998
+ by_surface,
11999
+ interpretation: "finding-is-not-proof-of-intent"
12000
+ };
12001
+ }
12002
+ function renderAuditStats(summary) {
12003
+ const out3 = [
12004
+ "TamperWard audit stats",
12005
+ "",
12006
+ `Events ${summary.events}`,
12007
+ `Blocked ${summary.blocked}`,
12008
+ `Warnings ${summary.warnings}`,
12009
+ `Sessions ${summary.sessions}`
12010
+ ];
12011
+ if (summary.first_event && summary.last_event) {
12012
+ out3.push(`First ${summary.first_event}`, `Last ${summary.last_event}`);
12013
+ }
12014
+ if (summary.by_rule.length) {
12015
+ out3.push("", "By rule");
12016
+ const width = Math.max(...summary.by_rule.map((row) => row.rule.length));
12017
+ for (const row of summary.by_rule) out3.push(` ${row.rule.padEnd(width)} ${row.events}`);
12018
+ }
12019
+ if (summary.by_surface.length) {
12020
+ out3.push("", "By surface");
12021
+ const width = Math.max(...summary.by_surface.map((row) => row.surface.length));
12022
+ for (const row of summary.by_surface) out3.push(` ${row.surface.padEnd(width)} ${row.events}`);
12023
+ }
12024
+ out3.push("", "Note: an integrity finding is a signal, not proof of agent intent; legitimate refactors can trigger findings.");
12025
+ return out3.join("\n") + "\n";
12026
+ }
12027
+ function runStats(opts = {}) {
12028
+ const cwd = resolve4(opts.cwd ?? process.cwd());
12029
+ const explicitFile = opts.file !== void 0;
12030
+ const configured = process.env[AUDIT_LOG_ENV];
12031
+ const file = opts.file ? isAbsolute8(opts.file) ? opts.file : resolve4(cwd, opts.file) : configured && configured !== "auto" ? isAbsolute8(configured) ? configured : resolve4(cwd, configured) : defaultAuditPath(cwd);
12032
+ const cutoff = opts.since ? parseSince(opts.since) : null;
12033
+ if (!file) throw new Error("stats needs --file outside a Git repository");
12034
+ if (!existsSync9(file)) {
12035
+ if (explicitFile) throw new Error(`audit file not found: ${file}`);
12036
+ const empty2 = summarizeAudit([]);
12037
+ process.stdout.write(opts.json ? JSON.stringify(empty2) + "\n" : renderAuditStats(empty2));
12038
+ return 0;
12039
+ }
12040
+ let events = parseAuditJsonl(readFileSync10(file, "utf8"));
12041
+ if (cutoff !== null) {
12042
+ events = events.filter((event) => Date.parse(event.timestamp) >= cutoff);
12043
+ }
12044
+ const summary = summarizeAudit(events);
12045
+ process.stdout.write(opts.json ? JSON.stringify(summary) + "\n" : renderAuditStats(summary));
12046
+ return 0;
12047
+ }
12048
+ var AUDIT_SCHEMA_VERSION, AUDIT_LOG_ENV, ALLOWED_KEYS;
12049
+ var init_audit = __esm({
12050
+ "src/cli/audit.ts"() {
12051
+ "use strict";
12052
+ init_repo_context();
12053
+ AUDIT_SCHEMA_VERSION = 1;
12054
+ AUDIT_LOG_ENV = "TAMPERWARD_AUDIT_LOG";
12055
+ ALLOWED_KEYS = /* @__PURE__ */ new Set([
12056
+ "schema_version",
12057
+ "id",
12058
+ "timestamp",
12059
+ "surface",
12060
+ "agent",
12061
+ "rule",
12062
+ "severity",
12063
+ "decision",
12064
+ "session"
12065
+ ]);
12066
+ }
12067
+ });
12068
+
11811
12069
  // src/cli/hook.ts
11812
- import { readFileSync as readFileSync10, appendFileSync as appendFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync9 } from "node:fs";
11813
- import { createHash as createHash5 } from "node:crypto";
12070
+ import { readFileSync as readFileSync11, appendFileSync as appendFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "node:fs";
12071
+ import { createHash as createHash6 } from "node:crypto";
11814
12072
  function setSnapshotCache(cache3) {
11815
12073
  snapshotCache = cache3;
11816
12074
  }
11817
12075
  function readStdin() {
11818
12076
  try {
11819
- return readFileSync10(0, "utf8");
12077
+ return readFileSync11(0, "utf8");
11820
12078
  } catch (e) {
11821
12079
  throw new HookInputError(`cannot read the hook payload on stdin: ${e instanceof Error ? e.message : String(e)}`);
11822
12080
  }
@@ -11844,7 +12102,7 @@ function recordDenylog(blocks2) {
11844
12102
  const log = process.env.TAMPERWARD_DENYLOG;
11845
12103
  if (!log) return;
11846
12104
  try {
11847
- appendFileSync4(log, blocks2.map((b) => b.rule).join(",") + "\n");
12105
+ appendFileSync5(log, blocks2.map((b) => b.rule).join(",") + "\n");
11848
12106
  } catch {
11849
12107
  }
11850
12108
  }
@@ -11852,7 +12110,7 @@ function errText2(e) {
11852
12110
  const m = e instanceof Error ? e.message : String(e);
11853
12111
  return m.length > 200 ? m.slice(0, 200) + "\u2026" : m;
11854
12112
  }
11855
- function failClosed(kind, detail) {
12113
+ function failClosed(kind, detail, audit) {
11856
12114
  const f = {
11857
12115
  rule: "tamperward-unavailable",
11858
12116
  severity: "block",
@@ -11861,12 +12119,9 @@ function failClosed(kind, detail) {
11861
12119
  remediation: "Repair the Tamperward setup \u2014 most often an unparseable .tamperward.yml (check for merge-conflict markers) \u2014 then retry. Do not work around the gate while it is down.",
11862
12120
  signoff: { required: true, command: 'tamperward allow --reason "..."' }
11863
12121
  };
11864
- return verdict([f], kind);
12122
+ return verdict([f], kind, audit);
11865
12123
  }
11866
- function verdict(blocks2, kind) {
11867
- if (blocks2.length === 0) return { exitCode: 0, stdout: "" };
11868
- recordDenylog(blocks2);
11869
- const reason = formatDenial(blocks2);
12124
+ function denyWire(reason, kind) {
11870
12125
  const payload = kind === "PreToolUse" ? {
11871
12126
  hookSpecificOutput: {
11872
12127
  hookEventName: "PreToolUse",
@@ -11874,7 +12129,20 @@ function verdict(blocks2, kind) {
11874
12129
  permissionDecisionReason: reason
11875
12130
  }
11876
12131
  } : { decision: "block", reason };
11877
- return { exitCode: 0, stdout: JSON.stringify(payload) + "\n" };
12132
+ return JSON.stringify(payload) + "\n";
12133
+ }
12134
+ function verdict(blocks2, kind, audit) {
12135
+ if (blocks2.length === 0) return { exitCode: 0, stdout: "" };
12136
+ recordDenylog(blocks2);
12137
+ if (audit) {
12138
+ recordAuditFindings(blocks2, {
12139
+ cwd: audit.cwd,
12140
+ surface: kind === "PreToolUse" ? "pretooluse" : "stop",
12141
+ sessionId: audit.sessionId
12142
+ });
12143
+ }
12144
+ const reason = formatDenial(blocks2);
12145
+ return { exitCode: 0, stdout: denyWire(reason, kind) };
11878
12146
  }
11879
12147
  function fileChanges(changes) {
11880
12148
  return changes.filter((c) => c.kind === "file");
@@ -12038,7 +12306,7 @@ function sanctionPredictedWrites(cwd, sessionId, policy, changes) {
12038
12306
  let dirty = false;
12039
12307
  for (const c of changes) {
12040
12308
  if (c.kind !== "file" || c.after == null || !isProtected(c.path, policy)) continue;
12041
- const hash = createHash5("sha256").update(c.after).digest("hex").slice(0, 16);
12309
+ const hash = createHash6("sha256").update(c.after).digest("hex").slice(0, 16);
12042
12310
  const prev = tree[c.path];
12043
12311
  tree[c.path] = {
12044
12312
  hash,
@@ -12052,20 +12320,24 @@ function sanctionPredictedWrites(cwd, sessionId, policy, changes) {
12052
12320
  }
12053
12321
  if (dirty) savePtree(cwd, sessionId, tree);
12054
12322
  }
12055
- function preToolUseVerdict(input, defaultCwd) {
12323
+ function preToolUseVerdict(input, defaultCwd, trustedRoot) {
12324
+ const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
12056
12325
  try {
12057
- const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
12326
+ if (trustedRoot !== void 0) {
12327
+ const v = validateClaimAgainstRoot(input.cwd, trustedRoot, defaultCwd ?? process.cwd());
12328
+ if (!v.ok) return failClosed("PreToolUse", `repository identity claim rejected: ${v.rejected}`);
12329
+ }
12058
12330
  const cwd = repoRoot(sessionCwd);
12059
12331
  turnBaseline(cwd, input.session_id);
12060
12332
  const policy = loadPolicy(cwd);
12061
12333
  const driftBlocks = effectDriftBlocks(cwd, input.session_id, policy);
12062
- if (driftBlocks) return verdict(driftBlocks, "PreToolUse");
12334
+ if (driftBlocks) return verdict(driftBlocks, "PreToolUse", { cwd, sessionId: input.session_id });
12063
12335
  const changes = changesFromClaudeHook(input, cwd, sessionCwd);
12064
12336
  const blocks2 = evaluate(changes, policy, void 0, "tool-call", { cwd }).filter((f) => f.severity === "block");
12065
12337
  if (blocks2.length === 0) sanctionPredictedWrites(cwd, input.session_id, policy, changes);
12066
- return verdict(blocks2, "PreToolUse");
12338
+ return verdict(blocks2, "PreToolUse", { cwd, sessionId: input.session_id });
12067
12339
  } catch (e) {
12068
- return failClosed("PreToolUse", errText2(e));
12340
+ return failClosed("PreToolUse", errText2(e), { cwd: sessionCwd, sessionId: input.session_id });
12069
12341
  }
12070
12342
  }
12071
12343
  function cursorPath(cwd, sessionId) {
@@ -12089,19 +12361,21 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
12089
12361
  const log = defaultEventLog(cwd);
12090
12362
  const telemetry = watcherTelemetry(log);
12091
12363
  if (telemetry.state !== "healthy") {
12092
- recordObserverHealth(telemetry.state, telemetry.reason);
12364
+ recordObserverHealth(cwd, sessionId, telemetry.state, telemetry.reason);
12093
12365
  }
12094
12366
  const cp = cursorPath(cwd, sessionId);
12095
- if (!existsSync9(log)) return none;
12367
+ if (!existsSync10(log)) return none;
12096
12368
  let offset = 0;
12097
- if (cp && existsSync9(cp)) {
12098
- const n = Number(readFileSync10(cp, "utf8"));
12369
+ if (cp && existsSync10(cp)) {
12370
+ const n = Number(readFileSync11(cp, "utf8"));
12099
12371
  offset = Number.isFinite(n) ? n : 0;
12100
12372
  }
12101
12373
  const drained = drainEvents(log, offset);
12102
12374
  if (!drained.complete) {
12103
12375
  const issue = drained.issue ?? "read-stalled";
12104
12376
  recordObserverHealth(
12377
+ cwd,
12378
+ sessionId,
12105
12379
  "degraded",
12106
12380
  `event telemetry was not fully classified (${issue}); Stop retained the prior cursor`
12107
12381
  );
@@ -12129,7 +12403,7 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
12129
12403
  return e.kind === "file" && e.content != null ? contentHash(e.content) : null;
12130
12404
  };
12131
12405
  const findings = transientFindings(events, persistent, policy, finalHash);
12132
- recordWarns(findings.filter((f) => f.severity !== "block"));
12406
+ recordWarns(cwd, sessionId, findings.filter((f) => f.severity !== "block"));
12133
12407
  return {
12134
12408
  blocks: findings.filter((f) => f.severity === "block"),
12135
12409
  commit: () => {
@@ -12140,20 +12414,23 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
12140
12414
  }
12141
12415
  };
12142
12416
  }
12143
- function recordWarns(warns) {
12417
+ function recordWarns(cwd, sessionId, warns) {
12418
+ if (warns.length === 0) return;
12419
+ recordAuditFindings(warns, { cwd, surface: "stop", sessionId });
12144
12420
  const log = process.env.TAMPERWARD_DENYLOG;
12145
- if (!log || warns.length === 0) return;
12421
+ if (!log) return;
12146
12422
  try {
12147
- appendFileSync4(log, warns.map((w) => `warn:${w.rule}:${w.file ?? ""}`).join("\n") + "\n");
12423
+ appendFileSync5(log, warns.map((w) => `warn:${w.rule}:${w.file ?? ""}`).join("\n") + "\n");
12148
12424
  } catch {
12149
12425
  }
12150
12426
  }
12151
- function recordObserverHealth(state, reason) {
12427
+ function recordObserverHealth(cwd, sessionId, state, reason) {
12428
+ recordAuditSignal("transient-observer", "warn", { cwd, surface: "stop", sessionId });
12152
12429
  const log = process.env.TAMPERWARD_DENYLOG;
12153
12430
  if (!log) return;
12154
12431
  const detail = (reason ?? "").replace(/[\r\n]+/g, " ").trim();
12155
12432
  try {
12156
- appendFileSync4(
12433
+ appendFileSync5(
12157
12434
  log,
12158
12435
  `warn:transient-observer:${state}${detail ? ":" + detail : ""}
12159
12436
  `
@@ -12161,9 +12438,13 @@ function recordObserverHealth(state, reason) {
12161
12438
  } catch {
12162
12439
  }
12163
12440
  }
12164
- function stopVerdict(input, defaultCwd) {
12441
+ function stopVerdict(input, defaultCwd, trustedRoot) {
12165
12442
  if (input.stop_hook_active) return { exitCode: 0, stdout: "" };
12166
12443
  const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
12444
+ if (trustedRoot !== void 0) {
12445
+ const v = validateClaimAgainstRoot(input.cwd, trustedRoot, defaultCwd ?? process.cwd());
12446
+ if (!v.ok) return failClosed("Stop", `repository identity claim rejected: ${v.rejected}`);
12447
+ }
12167
12448
  const ctx = repoContext(sessionCwd);
12168
12449
  if (!ctx) {
12169
12450
  const why = outsideRepository(sessionCwd);
@@ -12194,28 +12475,28 @@ function stopVerdict(input, defaultCwd) {
12194
12475
  saveTurnTree(cwd, input.session_id, current);
12195
12476
  }
12196
12477
  } catch (e) {
12197
- return failClosed("Stop", errText2(e));
12478
+ return failClosed("Stop", errText2(e), { cwd: sessionCwd, sessionId: input.session_id });
12198
12479
  }
12199
12480
  if (blocks2.length === 0) {
12200
12481
  advanceTurnBaseline(cwd, input.session_id);
12201
12482
  commitCursor();
12202
12483
  }
12203
- return verdict(blocks2, "Stop");
12484
+ return verdict(blocks2, "Stop", { cwd, sessionId: input.session_id });
12204
12485
  }
12205
12486
  function emit(r) {
12206
12487
  if (r.stdout) process.stdout.write(r.stdout);
12207
12488
  return r.exitCode;
12208
12489
  }
12209
- function preToolUseFromRaw(raw, defaultCwd) {
12490
+ function preToolUseFromRaw(raw, defaultCwd, trustedRoot) {
12210
12491
  try {
12211
- return preToolUseVerdict(parseInput(raw), defaultCwd);
12492
+ return preToolUseVerdict(parseInput(raw), defaultCwd, trustedRoot);
12212
12493
  } catch (e) {
12213
12494
  return failClosed("PreToolUse", errText2(e));
12214
12495
  }
12215
12496
  }
12216
- function stopFromRaw(raw, defaultCwd) {
12497
+ function stopFromRaw(raw, defaultCwd, trustedRoot) {
12217
12498
  try {
12218
- return stopVerdict(parseInput(raw), defaultCwd);
12499
+ return stopVerdict(parseInput(raw), defaultCwd, trustedRoot);
12219
12500
  } catch (e) {
12220
12501
  return failClosed("Stop", errText2(e));
12221
12502
  }
@@ -12258,6 +12539,7 @@ var init_hook = __esm({
12258
12539
  init_disk();
12259
12540
  init_narrow();
12260
12541
  init_repo_context();
12542
+ init_audit();
12261
12543
  snapshotCache = null;
12262
12544
  HookInputError = class extends Error {
12263
12545
  };
@@ -12266,7 +12548,7 @@ var init_hook = __esm({
12266
12548
 
12267
12549
  // src/ptree-cache.ts
12268
12550
  import { lstatSync as lstatSync5 } from "node:fs";
12269
- import { join as join13 } from "node:path";
12551
+ import { join as join14 } from "node:path";
12270
12552
  function keyOf(st) {
12271
12553
  return `${st.dev}:${st.ino}:${st.size}:${st.mode}:${st.mtimeNs}:${st.ctimeNs}`;
12272
12554
  }
@@ -12306,7 +12588,7 @@ var init_ptree_cache = __esm({
12306
12588
  /** A path the caller has reason to distrust (an fs event, a sanctioned edit):
12307
12589
  * its entry is forgotten so the next walk hashes it whatever its stat says. */
12308
12590
  markDirty(cwd, rel) {
12309
- this.map.delete(join13(cwd, rel));
12591
+ this.map.delete(join14(cwd, rel));
12310
12592
  }
12311
12593
  /**
12312
12594
  * The snapshot entry for `rel`, from the cache when the six-field stat key
@@ -12316,7 +12598,7 @@ var init_ptree_cache = __esm({
12316
12598
  * time, the way the in-process path treats them.
12317
12599
  */
12318
12600
  entry(cwd, rel, compute) {
12319
- const abs = join13(cwd, rel);
12601
+ const abs = join14(cwd, rel);
12320
12602
  let st;
12321
12603
  try {
12322
12604
  st = lstatSync5(abs, { bigint: true });
@@ -12359,13 +12641,13 @@ var init_ptree_cache = __esm({
12359
12641
  });
12360
12642
 
12361
12643
  // src/cli/hook-service.ts
12362
- import { chmodSync, existsSync as existsSync10, lstatSync as lstatSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync11, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
12644
+ import { chmodSync, existsSync as existsSync11, lstatSync as lstatSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync12, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
12363
12645
  import { createConnection as createConnection2, createServer } from "node:net";
12364
- import { realpathSync as realpathSync3 } from "node:fs";
12646
+ import { realpathSync as realpathSync4 } from "node:fs";
12365
12647
  import { sep } from "node:path";
12366
12648
  function readServiceState(paths) {
12367
12649
  try {
12368
- const v = JSON.parse(readFileSync11(paths.state, "utf8"));
12650
+ const v = JSON.parse(readFileSync12(paths.state, "utf8"));
12369
12651
  if (!isRecord(v)) return null;
12370
12652
  const { pid, version, root, started_at } = v;
12371
12653
  if (typeof pid !== "number" || typeof version !== "string" || typeof root !== "string" || typeof started_at !== "string") return null;
@@ -12384,12 +12666,12 @@ function pidAlive2(pid) {
12384
12666
  }
12385
12667
  }
12386
12668
  function listening(socket) {
12387
- return new Promise((resolve16) => {
12669
+ return new Promise((resolve17) => {
12388
12670
  const sock = createConnection2(socket);
12389
12671
  const done = (v) => {
12390
12672
  clearTimeout(timer);
12391
12673
  sock.destroy();
12392
- resolve16(v);
12674
+ resolve17(v);
12393
12675
  };
12394
12676
  const timer = setTimeout(() => done(false), 1e3);
12395
12677
  sock.once("connect", () => done(true));
@@ -12399,7 +12681,7 @@ function listening(socket) {
12399
12681
  function within(root, cwd) {
12400
12682
  let real;
12401
12683
  try {
12402
- real = realpathSync3(cwd);
12684
+ real = realpathSync4(cwd);
12403
12685
  } catch {
12404
12686
  return false;
12405
12687
  }
@@ -12447,7 +12729,7 @@ function removeQuietly(p) {
12447
12729
  }
12448
12730
  }
12449
12731
  function ensurePrivateDir(dir, uid) {
12450
- if (!existsSync10(dir)) mkdirSync5(dir, { recursive: true, mode: 448 });
12732
+ if (!existsSync11(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
12451
12733
  const st = lstatSync6(dir);
12452
12734
  if (!st.isDirectory()) throw new Error(`${dir} is not a directory`);
12453
12735
  if (st.uid !== uid) throw new Error(`${dir} is owned by uid ${st.uid}, not ${uid}; refusing to serve from it`);
@@ -12464,7 +12746,7 @@ async function startHookService(opts) {
12464
12746
  if (uid === void 0) throw new Error("no uid on this platform; refusing to start");
12465
12747
  let root;
12466
12748
  try {
12467
- root = realpathSync3(repoRoot(realpathSync3(opts.root)));
12749
+ root = realpathSync4(repoRoot(realpathSync4(opts.root)));
12468
12750
  } catch {
12469
12751
  throw new Error(`cannot resolve ${opts.root}`);
12470
12752
  }
@@ -12542,11 +12824,11 @@ async function startHookService(opts) {
12542
12824
  });
12543
12825
  const umask = process.umask(63);
12544
12826
  try {
12545
- await new Promise((resolve16, reject) => {
12827
+ await new Promise((resolve17, reject) => {
12546
12828
  server.once("error", reject);
12547
12829
  server.listen(paths.socket, () => {
12548
12830
  server.off("error", reject);
12549
- resolve16();
12831
+ resolve17();
12550
12832
  });
12551
12833
  });
12552
12834
  } finally {
@@ -12563,14 +12845,14 @@ async function startHookService(opts) {
12563
12845
  get served() {
12564
12846
  return served;
12565
12847
  },
12566
- close: () => new Promise((resolve16) => {
12567
- if (closed) return resolve16();
12848
+ close: () => new Promise((resolve17) => {
12849
+ if (closed) return resolve17();
12568
12850
  closed = true;
12569
12851
  setSnapshotCache(null);
12570
12852
  server.close(() => {
12571
12853
  removeQuietly(paths.socket);
12572
12854
  removeQuietly(paths.state);
12573
- resolve16();
12855
+ resolve17();
12574
12856
  });
12575
12857
  })
12576
12858
  };
@@ -12599,7 +12881,7 @@ async function stopHookService(paths) {
12599
12881
  process.kill(state.pid, "SIGTERM");
12600
12882
  } catch {
12601
12883
  }
12602
- const gone = () => !pidAlive2(state.pid) || !existsSync10(paths.socket);
12884
+ const gone = () => !pidAlive2(state.pid) || !existsSync11(paths.socket);
12603
12885
  const until = Date.now() + STOP_WAIT_MS;
12604
12886
  while (!gone() && Date.now() < until) sleepSync(50);
12605
12887
  if (!gone()) throw new Error(`hook service pid ${state.pid} did not exit within ${STOP_WAIT_MS / 1e3}s`);
@@ -12784,7 +13066,7 @@ var init_verifier_limits = __esm({
12784
13066
  });
12785
13067
 
12786
13068
  // src/safe-write.ts
12787
- import { randomBytes } from "node:crypto";
13069
+ import { randomBytes as randomBytes2 } from "node:crypto";
12788
13070
  import { lstatSync as lstatSync7, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
12789
13071
  function writeTargetKind(path) {
12790
13072
  let st;
@@ -12813,7 +13095,7 @@ function existingMode(path, fallback) {
12813
13095
  return lstatSync7(path).mode & 511;
12814
13096
  }
12815
13097
  function atomicReplaceFile(path, content, mode) {
12816
- const tmp = `${path}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
13098
+ const tmp = `${path}.${process.pid}.${randomBytes2(8).toString("hex")}.tmp`;
12817
13099
  try {
12818
13100
  writeFileSync7(tmp, content, { encoding: "utf8", flag: "wx", mode });
12819
13101
  renameSync3(tmp, path);
@@ -12830,9 +13112,9 @@ var init_safe_write = __esm({
12830
13112
 
12831
13113
  // src/cli/init.ts
12832
13114
  import { execFileSync as execFileSync6 } from "node:child_process";
12833
- import { createHash as createHash6 } from "node:crypto";
12834
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, realpathSync as realpathSync4 } from "node:fs";
12835
- import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute8, join as join14, relative as relative3, resolve as resolve4 } from "node:path";
13115
+ import { createHash as createHash7 } from "node:crypto";
13116
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync13, realpathSync as realpathSync5 } from "node:fs";
13117
+ import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute9, join as join15, relative as relative3, resolve as resolve5 } from "node:path";
12836
13118
  function gitConfig(cwd, key2) {
12837
13119
  try {
12838
13120
  return execFileSync6("git", ["config", "--get", key2], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
@@ -12880,7 +13162,7 @@ function stalePin(command) {
12880
13162
  return pin !== null && (pin !== TW_VERSION || !OURS.test(command));
12881
13163
  }
12882
13164
  function bodyHash(body) {
12883
- return createHash6("sha256").update(body).digest("hex").slice(0, 16);
13165
+ return createHash7("sha256").update(body).digest("hex").slice(0, 16);
12884
13166
  }
12885
13167
  function workflowFile(body) {
12886
13168
  return `${WORKFLOW_MARK} v${TW_VERSION} sha256:${bodyHash(body)}
@@ -12925,34 +13207,34 @@ function verifierCandidates(cwd) {
12925
13207
  if (!candidates.includes(command)) candidates.push(command);
12926
13208
  };
12927
13209
  try {
12928
- const pkg = JSON.parse(readFileSync12(join14(cwd, "package.json"), "utf8"));
13210
+ const pkg = JSON.parse(readFileSync13(join15(cwd, "package.json"), "utf8"));
12929
13211
  const test = isRecord(pkg) && isRecord(pkg.scripts) ? pkg.scripts.test : void 0;
12930
13212
  if (typeof test === "string" && test.trim() !== "" && !/error:\s*no test specified/i.test(test)) add("npm test");
12931
13213
  } catch {
12932
13214
  }
12933
13215
  try {
12934
- const pyproject = readFileSync12(join14(cwd, "pyproject.toml"), "utf8");
13216
+ const pyproject = readFileSync13(join15(cwd, "pyproject.toml"), "utf8");
12935
13217
  if (/^\s*\[tool\.pytest(?:\.ini_options)?\]\s*$/m.test(pyproject)) add("pytest");
12936
13218
  } catch {
12937
13219
  }
12938
- if (existsSync11(join14(cwd, "pytest.ini"))) add("pytest");
13220
+ if (existsSync12(join15(cwd, "pytest.ini"))) add("pytest");
12939
13221
  try {
12940
- const setupCfg = readFileSync12(join14(cwd, "setup.cfg"), "utf8");
13222
+ const setupCfg = readFileSync13(join15(cwd, "setup.cfg"), "utf8");
12941
13223
  if (/^\s*\[tool:pytest\]\s*$/m.test(setupCfg)) add("pytest");
12942
13224
  } catch {
12943
13225
  }
12944
13226
  try {
12945
- const tox = readFileSync12(join14(cwd, "tox.ini"), "utf8");
13227
+ const tox = readFileSync13(join15(cwd, "tox.ini"), "utf8");
12946
13228
  if (/^\s*\[tox\]\s*$/m.test(tox)) add("tox");
12947
13229
  } catch {
12948
13230
  }
12949
13231
  try {
12950
- const cargo = readFileSync12(join14(cwd, "Cargo.toml"), "utf8");
13232
+ const cargo = readFileSync13(join15(cwd, "Cargo.toml"), "utf8");
12951
13233
  if (/^\s*\[(?:package|workspace)\]\s*(?:#.*)?$/m.test(cargo)) add("cargo test");
12952
13234
  } catch {
12953
13235
  }
12954
13236
  try {
12955
- const goMod = readFileSync12(join14(cwd, "go.mod"), "utf8");
13237
+ const goMod = readFileSync13(join15(cwd, "go.mod"), "utf8");
12956
13238
  if (/^\s*module\s+\S+\s*(?:\/\/.*)?$/m.test(goMod)) add("go test ./...");
12957
13239
  } catch {
12958
13240
  }
@@ -12992,10 +13274,10 @@ function verifierSetupMessage(cwd) {
12992
13274
  return lines.join("\n");
12993
13275
  }
12994
13276
  function planPolicy(cwd) {
12995
- const path = join14(cwd, POLICY_FILE);
13277
+ const path = join15(cwd, POLICY_FILE);
12996
13278
  const refused = refuseNonRegular(path);
12997
13279
  if (refused) return { item: "policy", path: POLICY_FILE, status: "error", detail: refused };
12998
- if (existsSync11(path)) {
13280
+ if (existsSync12(path)) {
12999
13281
  try {
13000
13282
  loadPolicy(cwd);
13001
13283
  } catch (e) {
@@ -13013,8 +13295,8 @@ function planPolicy(cwd) {
13013
13295
  }
13014
13296
  function planGitignore(cwd) {
13015
13297
  const rel = ".gitignore";
13016
- const path = join14(cwd, rel);
13017
- if (!existsSync11(join14(cwd, "node_modules"))) return null;
13298
+ const path = join15(cwd, rel);
13299
+ if (!existsSync12(join15(cwd, "node_modules"))) return null;
13018
13300
  let existing = "";
13019
13301
  let kind;
13020
13302
  try {
@@ -13024,7 +13306,7 @@ function planGitignore(cwd) {
13024
13306
  }
13025
13307
  const refused = refuseNonRegular(path);
13026
13308
  if (refused) return { item: "gitignore", path: rel, status: "error", detail: refused };
13027
- if (kind === "file") existing = readFileSync12(path, "utf8");
13309
+ if (kind === "file") existing = readFileSync13(path, "utf8");
13028
13310
  try {
13029
13311
  const tracked = execFileSync6("git", ["ls-files", "--cached", "--", "node_modules"], {
13030
13312
  cwd,
@@ -13075,14 +13357,14 @@ function gateVerdicts(event, groups, needle) {
13075
13357
  }
13076
13358
  function planClaudeHooks(cwd) {
13077
13359
  const rel = ".claude/settings.json";
13078
- const path = join14(cwd, rel);
13360
+ const path = join15(cwd, rel);
13079
13361
  const refused = refuseNonRegular(path);
13080
13362
  if (refused) return { item: "agent", path: rel, status: "error", detail: refused };
13081
13363
  let settings = {};
13082
- if (existsSync11(path)) {
13364
+ if (existsSync12(path)) {
13083
13365
  let parsed;
13084
13366
  try {
13085
- parsed = JSON.parse(readFileSync12(path, "utf8"));
13367
+ parsed = JSON.parse(readFileSync13(path, "utf8"));
13086
13368
  } catch {
13087
13369
  return {
13088
13370
  item: "agent",
@@ -13131,7 +13413,7 @@ function planClaudeHooks(cwd) {
13131
13413
  return {
13132
13414
  item: "agent",
13133
13415
  path: rel,
13134
- status: existsSync11(path) ? "update" : "create",
13416
+ status: existsSync12(path) ? "update" : "create",
13135
13417
  detail: [
13136
13418
  needPre && "wire PreToolUse deny",
13137
13419
  needStop && "wire Stop sweep",
@@ -13164,7 +13446,7 @@ function planClaudeHooks(cwd) {
13164
13446
  (hooks.Stop ??= []).push({ hooks: [{ type: "command", command: SWEEP_CMD }] });
13165
13447
  }
13166
13448
  if (needDisableFalse) settings.disableAllHooks = false;
13167
- mkdirSync6(dirname4(path), { recursive: true });
13449
+ mkdirSync7(dirname5(path), { recursive: true });
13168
13450
  atomicReplaceFile(path, JSON.stringify(settings, null, 2) + "\n", existingMode(path, 420));
13169
13451
  }
13170
13452
  };
@@ -13172,14 +13454,14 @@ function planClaudeHooks(cwd) {
13172
13454
  function hooksDir(cwd) {
13173
13455
  try {
13174
13456
  const out3 = execFileSync6("git", ["rev-parse", "--git-path", "hooks"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
13175
- if (out3) return resolve4(cwd, out3);
13457
+ if (out3) return resolve5(cwd, out3);
13176
13458
  } catch {
13177
13459
  }
13178
- return existsSync11(join14(cwd, ".git")) ? join14(cwd, ".git", "hooks") : null;
13460
+ return existsSync12(join15(cwd, ".git")) ? join15(cwd, ".git", "hooks") : null;
13179
13461
  }
13180
13462
  function display(cwd, path) {
13181
13463
  const rel = relative3(cwd, path);
13182
- return rel && !rel.startsWith("..") && !isAbsolute8(rel) ? rel : path;
13464
+ return rel && !rel.startsWith("..") && !isAbsolute9(rel) ? rel : path;
13183
13465
  }
13184
13466
  function unconditionalExitAt(lines) {
13185
13467
  let depth = 0;
@@ -13202,29 +13484,29 @@ function planPreCommit(cwd) {
13202
13484
  const skip = (detail) => ({ item: "pre-commit", path: "(none)", status: "skip", detail });
13203
13485
  let path;
13204
13486
  let note;
13205
- if (existsSync11(join14(cwd, ".husky"))) {
13206
- path = join14(cwd, ".husky", "pre-commit");
13487
+ if (existsSync12(join15(cwd, ".husky"))) {
13488
+ path = join15(cwd, ".husky", "pre-commit");
13207
13489
  note = "husky";
13208
13490
  } else {
13209
- const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync11(join14(cwd, c))));
13491
+ const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync12(join15(cwd, c))));
13210
13492
  if (managed) {
13211
13493
  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}`);
13212
13494
  }
13213
13495
  const dir = hooksDir(cwd);
13214
13496
  if (!dir) return skip("not a git repo and no .husky/ \u2014 nothing to wire");
13215
- if (basename3(dir) === "_" && basename3(dirname4(dir)) === ".husky") {
13216
- path = join14(dirname4(dir), "pre-commit");
13497
+ if (basename3(dir) === "_" && basename3(dirname5(dir)) === ".husky") {
13498
+ path = join15(dirname5(dir), "pre-commit");
13217
13499
  note = "husky";
13218
13500
  } else {
13219
- path = join14(dir, "pre-commit");
13501
+ path = join15(dir, "pre-commit");
13220
13502
  const rel2 = display(cwd, dir);
13221
- note = rel2 === join14(".git", "hooks") ? "plain git hook (local-only: .git/hooks is not committed \u2014 consider husky to share it)" : `git hooks directory ${rel2} (core.hooksPath)`;
13503
+ note = rel2 === join15(".git", "hooks") ? "plain git hook (local-only: .git/hooks is not committed \u2014 consider husky to share it)" : `git hooks directory ${rel2} (core.hooksPath)`;
13222
13504
  }
13223
13505
  }
13224
13506
  const rel = display(cwd, path);
13225
13507
  const refused = refuseNonRegular(path);
13226
13508
  if (refused) return { item: "pre-commit", path: rel, status: "error", detail: refused };
13227
- const existing = existsSync11(path) ? readFileSync12(path, "utf8") : null;
13509
+ const existing = existsSync12(path) ? readFileSync13(path, "utf8") : null;
13228
13510
  const hookMode = () => existingMode(path, 493) | 73;
13229
13511
  if (existing === null) {
13230
13512
  return {
@@ -13233,7 +13515,7 @@ function planPreCommit(cwd) {
13233
13515
  status: "create",
13234
13516
  detail: `create via ${note}`,
13235
13517
  apply: () => {
13236
- mkdirSync6(dirname4(path), { recursive: true });
13518
+ mkdirSync7(dirname5(path), { recursive: true });
13237
13519
  atomicReplaceFile(path, `#!/bin/sh
13238
13520
  ${block.join("\n")}
13239
13521
  `, hookMode());
@@ -13310,12 +13592,12 @@ ${block.join("\n")}
13310
13592
  function planCodeowners(cwd) {
13311
13593
  const rel = ".github/CODEOWNERS";
13312
13594
  const existingRel = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"].find(
13313
- (r) => writeTargetKind(join14(cwd, r)) !== "absent"
13595
+ (r) => writeTargetKind(join15(cwd, r)) !== "absent"
13314
13596
  );
13315
- const path = join14(cwd, existingRel ?? rel);
13597
+ const path = join15(cwd, existingRel ?? rel);
13316
13598
  const refused = refuseNonRegular(path);
13317
13599
  if (refused) return { item: "codeowners", path: existingRel ?? rel, status: "error", detail: refused };
13318
- const existing = existingRel ? readFileSync12(path, "utf8") : null;
13600
+ const existing = existingRel ? readFileSync13(path, "utf8") : null;
13319
13601
  const missing = CODEOWNERS_PATHS.filter((p) => existing === null || !coveredBy(existing, p));
13320
13602
  if (existingRel !== void 0 && missing.length === 0) {
13321
13603
  return { item: "codeowners", path: existingRel, status: "ok", detail: "gate paths already have code owners" };
@@ -13341,21 +13623,21 @@ ${CODEOWNERS_MARK}
13341
13623
  warning: `${inferred.owner} was inferred from the origin remote and is not confirmed to be a user. If "${inferred.owner.slice(1)}" is an organisation, replace it with ${inferred.owner}/<team> in CODEOWNERS: GitHub rejects a bare organisation as an owner, and a rule with an invalid owner protects nothing.`
13342
13624
  } : {},
13343
13625
  apply: () => {
13344
- mkdirSync6(dirname4(path), { recursive: true });
13626
+ mkdirSync7(dirname5(path), { recursive: true });
13345
13627
  atomicReplaceFile(path, existing === null ? block.replace(/^\n/, "") : existing.replace(/\n?$/, "\n") + block, existingMode(path, 420));
13346
13628
  }
13347
13629
  };
13348
13630
  }
13349
13631
  function planWorkflow(cwd, force) {
13350
13632
  const rel = ".github/workflows/tamperward.yml";
13351
- const path = join14(cwd, rel);
13633
+ const path = join15(cwd, rel);
13352
13634
  const write = () => {
13353
- mkdirSync6(dirname4(path), { recursive: true });
13635
+ mkdirSync7(dirname5(path), { recursive: true });
13354
13636
  atomicReplaceFile(path, workflowFile(WORKFLOW_CONTENT), existingMode(path, 420));
13355
13637
  };
13356
13638
  const refused = refuseNonRegular(path);
13357
13639
  if (refused) return { item: "ci", path: rel, status: "error", detail: refused };
13358
- if (!existsSync11(path)) {
13640
+ if (!existsSync12(path)) {
13359
13641
  return {
13360
13642
  item: "ci",
13361
13643
  path: rel,
@@ -13366,7 +13648,7 @@ function planWorkflow(cwd, force) {
13366
13648
  }
13367
13649
  let src;
13368
13650
  try {
13369
- src = readFileSync12(path, "utf8");
13651
+ src = readFileSync13(path, "utf8");
13370
13652
  } catch {
13371
13653
  return { item: "ci", path: rel, status: "error", detail: "exists but cannot be read \u2014 refusing to overwrite" };
13372
13654
  }
@@ -13409,7 +13691,7 @@ function planned(item, path, plan) {
13409
13691
  }
13410
13692
  function sameDir(a, b) {
13411
13693
  try {
13412
- return realpathSync4(a) === realpathSync4(b);
13694
+ return realpathSync5(a) === realpathSync5(b);
13413
13695
  } catch {
13414
13696
  return false;
13415
13697
  }
@@ -13427,7 +13709,7 @@ function planInit(cwd, opts = {}) {
13427
13709
  ];
13428
13710
  }
13429
13711
  function runInit(opts) {
13430
- const requested = resolve4(opts.cwd ?? process.cwd());
13712
+ const requested = resolve5(opts.cwd ?? process.cwd());
13431
13713
  const cwd = repoRoot(requested);
13432
13714
  const w = opts.quiet ? { write: (_text) => true } : process.stdout;
13433
13715
  if (cwd !== requested && !sameDir(cwd, requested)) {
@@ -13625,7 +13907,7 @@ jobs:
13625
13907
 
13626
13908
  // src/fingerprint.ts
13627
13909
  import { execFileSync as execFileSync7 } from "node:child_process";
13628
- import { createHash as createHash7 } from "node:crypto";
13910
+ import { createHash as createHash8 } from "node:crypto";
13629
13911
  function treeFingerprint(cwd, keepIgnored) {
13630
13912
  const listed = execFileSync7("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
13631
13913
  cwd,
@@ -13634,7 +13916,7 @@ function treeFingerprint(cwd, keepIgnored) {
13634
13916
  env: trustedGitEnv()
13635
13917
  }).split("\0").filter(Boolean);
13636
13918
  const files = [.../* @__PURE__ */ new Set([...listed, ...keepIgnored ? ignoredPaths({ cwd }, keepIgnored) : []])].sort();
13637
- const h = createHash7("sha256");
13919
+ const h = createHash8("sha256");
13638
13920
  for (const rel of files) {
13639
13921
  h.update(rel);
13640
13922
  h.update("\0");
@@ -13658,32 +13940,32 @@ var init_fingerprint = __esm({
13658
13940
  });
13659
13941
 
13660
13942
  // src/dependency-env.ts
13661
- import { createHash as createHash8 } from "node:crypto";
13943
+ import { createHash as createHash9 } from "node:crypto";
13662
13944
  import {
13663
- existsSync as existsSync12,
13945
+ existsSync as existsSync13,
13664
13946
  lstatSync as lstatSync8,
13665
13947
  readdirSync as readdirSync3,
13666
- readFileSync as readFileSync13,
13948
+ readFileSync as readFileSync14,
13667
13949
  readlinkSync as readlinkSync2,
13668
- realpathSync as realpathSync5,
13950
+ realpathSync as realpathSync6,
13669
13951
  statSync as statSync3
13670
13952
  } from "node:fs";
13671
13953
  import {
13672
13954
  basename as basename4,
13673
13955
  delimiter,
13674
- dirname as dirname5,
13675
- isAbsolute as isAbsolute9,
13676
- join as join15,
13956
+ dirname as dirname6,
13957
+ isAbsolute as isAbsolute10,
13958
+ join as join16,
13677
13959
  relative as relative4,
13678
- resolve as resolve5,
13960
+ resolve as resolve6,
13679
13961
  sep as sep2
13680
13962
  } from "node:path";
13681
13963
  function metricsFor(descriptor) {
13682
13964
  return descriptor.metrics ??= { fullSnapshots: 0, reusedSnapshots: 0, totalMs: 0 };
13683
13965
  }
13684
13966
  function inside(root, candidate) {
13685
- const rel = relative4(resolve5(root), resolve5(candidate));
13686
- return rel === "" || rel !== ".." && !rel.startsWith(".." + sep2) && !isAbsolute9(rel);
13967
+ const rel = relative4(resolve6(root), resolve6(candidate));
13968
+ return rel === "" || rel !== ".." && !rel.startsWith(".." + sep2) && !isAbsolute10(rel);
13687
13969
  }
13688
13970
  function modeKind(path) {
13689
13971
  const st = lstatSync8(path);
@@ -13697,7 +13979,7 @@ function fingerprintRoot(root, cwd, h) {
13697
13979
  let currentReal;
13698
13980
  try {
13699
13981
  rootStat = lstatSync8(root.path);
13700
- currentReal = realpathSync5(root.path);
13982
+ currentReal = realpathSync6(root.path);
13701
13983
  } catch (e) {
13702
13984
  throw new Unattestable(
13703
13985
  root.kind + " root " + JSON.stringify(root.path) + " can no longer be resolved (" + (e instanceof Error ? e.message : String(e)) + ")"
@@ -13713,7 +13995,7 @@ function fingerprintRoot(root, cwd, h) {
13713
13995
  }
13714
13996
  h.update("root\0" + root.kind + "\0" + root.path + "\0" + root.realPath + "\0" + String(rootStat.mode) + "\0");
13715
13997
  if (rootStat.isSymbolicLink()) h.update(readlinkSync2(root.path) + "\0");
13716
- const cwdReal = realpathSync5(cwd);
13998
+ const cwdReal = realpathSync6(cwd);
13717
13999
  const visited = /* @__PURE__ */ new Set();
13718
14000
  const hashFile = (path, label) => {
13719
14001
  let st;
@@ -13721,7 +14003,7 @@ function fingerprintRoot(root, cwd, h) {
13721
14003
  st = statSync3(path);
13722
14004
  if (!st.isFile()) throw new Error("not a regular file");
13723
14005
  h.update(label + "\0external-file\0" + path + "\0" + String(st.mode) + "\0");
13724
- h.update(readFileSync13(path));
14006
+ h.update(readFileSync14(path));
13725
14007
  h.update("\0");
13726
14008
  } catch (e) {
13727
14009
  throw new Unattestable(
@@ -13730,7 +14012,7 @@ function fingerprintRoot(root, cwd, h) {
13730
14012
  }
13731
14013
  };
13732
14014
  const walk2 = (dir, namespace) => {
13733
- const realDir = realpathSync5(dir);
14015
+ const realDir = realpathSync6(dir);
13734
14016
  if (visited.has(realDir)) return;
13735
14017
  visited.add(realDir);
13736
14018
  let entries;
@@ -13742,7 +14024,7 @@ function fingerprintRoot(root, cwd, h) {
13742
14024
  );
13743
14025
  }
13744
14026
  for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
13745
- const path = join15(dir, entry.name);
14027
+ const path = join16(dir, entry.name);
13746
14028
  const rel = namespace ? namespace + "/" + entry.name : entry.name;
13747
14029
  let ident;
13748
14030
  try {
@@ -13755,7 +14037,7 @@ function fingerprintRoot(root, cwd, h) {
13755
14037
  h.update(rel + "\0" + ident.kind + "\0" + String(ident.mode) + "\0");
13756
14038
  if (ident.kind === "file") {
13757
14039
  try {
13758
- h.update(readFileSync13(path));
14040
+ h.update(readFileSync14(path));
13759
14041
  h.update("\0");
13760
14042
  } catch (e) {
13761
14043
  throw new Unattestable(
@@ -13780,7 +14062,7 @@ function fingerprintRoot(root, cwd, h) {
13780
14062
  h.update(target + "\0");
13781
14063
  let resolved;
13782
14064
  try {
13783
- resolved = realpathSync5(path);
14065
+ resolved = realpathSync6(path);
13784
14066
  } catch {
13785
14067
  continue;
13786
14068
  }
@@ -13811,10 +14093,10 @@ function fingerprintRoot(root, cwd, h) {
13811
14093
  walk2(root.realPath, "");
13812
14094
  }
13813
14095
  function snapshot(roots, probes, cwd) {
13814
- const h = createHash8("sha256");
14096
+ const h = createHash9("sha256");
13815
14097
  for (const probe of probes) {
13816
14098
  h.update(
13817
- "probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync12(probe.path) ? "present" : "absent") + "\0"
14099
+ "probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync13(probe.path) ? "present" : "absent") + "\0"
13818
14100
  );
13819
14101
  }
13820
14102
  for (const root of roots) fingerprintRoot(root, cwd, h);
@@ -13835,7 +14117,7 @@ function freezeRoot(kind, path) {
13835
14117
  if (!statSync3(path).isDirectory()) {
13836
14118
  throw new Error("not a directory");
13837
14119
  }
13838
- return { kind, path: resolve5(path), realPath: realpathSync5(path) };
14120
+ return { kind, path: resolve6(path), realPath: realpathSync6(path) };
13839
14121
  } catch (e) {
13840
14122
  throw new Unattestable(
13841
14123
  kind + " root " + JSON.stringify(path) + " is not resolvable (" + (e instanceof Error ? e.message : String(e)) + ")"
@@ -13871,17 +14153,17 @@ function firstExecutable(command) {
13871
14153
  function resolveExecutable(token, cwd, env) {
13872
14154
  if (!token) return null;
13873
14155
  if (token.includes("/") || token.includes("\\")) {
13874
- const path = isAbsolute9(token) ? token : resolve5(cwd, token);
14156
+ const path = isAbsolute10(token) ? token : resolve6(cwd, token);
13875
14157
  try {
13876
- return existsSync12(path) ? realpathSync5(path) : null;
14158
+ return existsSync13(path) ? realpathSync6(path) : null;
13877
14159
  } catch {
13878
14160
  return null;
13879
14161
  }
13880
14162
  }
13881
14163
  for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
13882
- const candidate = resolve5(entry, token);
14164
+ const candidate = resolve6(entry, token);
13883
14165
  try {
13884
- if (existsSync12(candidate) && statSync3(candidate).isFile()) return realpathSync5(candidate);
14166
+ if (existsSync13(candidate) && statSync3(candidate).isFile()) return realpathSync6(candidate);
13885
14167
  } catch {
13886
14168
  }
13887
14169
  }
@@ -13898,13 +14180,13 @@ function looksPython(command, executable, token) {
13898
14180
  }
13899
14181
  function inferredVenvFromToken(token, cwd) {
13900
14182
  if (!token || !token.includes("/") && !token.includes("\\")) return null;
13901
- const path = isAbsolute9(token) ? token : resolve5(cwd, token);
14183
+ const path = isAbsolute10(token) ? token : resolve6(cwd, token);
13902
14184
  if (!pythonName(basename4(path))) return null;
13903
- const parent = dirname5(path);
14185
+ const parent = dirname6(path);
13904
14186
  const parentName = basename4(parent).toLowerCase();
13905
14187
  if (parentName !== "bin" && parentName !== "scripts") return null;
13906
- const root = dirname5(parent);
13907
- return existsSync12(join15(root, "pyvenv.cfg")) ? root : null;
14188
+ const root = dirname6(parent);
14189
+ return existsSync13(join16(root, "pyvenv.cfg")) ? root : null;
13908
14190
  }
13909
14191
  function unsupportedEcosystem(command, token) {
13910
14192
  const name = token ? basename4(token).toLowerCase() : "";
@@ -13916,17 +14198,17 @@ function unsupportedEcosystem(command, token) {
13916
14198
  }
13917
14199
  function inferredVenv(executable) {
13918
14200
  if (!executable) return null;
13919
- const parent = dirname5(executable);
14201
+ const parent = dirname6(executable);
13920
14202
  const parentName = basename4(parent).toLowerCase();
13921
14203
  if (parentName !== "bin" && parentName !== "scripts") return null;
13922
- const root = dirname5(parent);
13923
- return existsSync12(join15(root, "pyvenv.cfg")) ? root : null;
14204
+ const root = dirname6(parent);
14205
+ return existsSync13(join16(root, "pyvenv.cfg")) ? root : null;
13924
14206
  }
13925
14207
  function venvProblem(root) {
13926
- const cfg = join15(root, "pyvenv.cfg");
13927
- if (!existsSync12(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
14208
+ const cfg = join16(root, "pyvenv.cfg");
14209
+ if (!existsSync13(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
13928
14210
  try {
13929
- const body = readFileSync13(cfg, "utf8");
14211
+ const body = readFileSync14(cfg, "utf8");
13930
14212
  if (/^\s*include-system-site-packages\s*=\s*true\s*$/im.test(body)) {
13931
14213
  return "Python environment " + JSON.stringify(root) + " enables system site-packages; those external package roots are not bounded";
13932
14214
  }
@@ -13936,7 +14218,7 @@ function venvProblem(root) {
13936
14218
  return null;
13937
14219
  }
13938
14220
  function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
13939
- const cwd = resolve5(cwdInput);
14221
+ const cwd = resolve6(cwdInput);
13940
14222
  const roots = [];
13941
14223
  const probes = [];
13942
14224
  const seen = /* @__PURE__ */ new Set();
@@ -13950,21 +14232,21 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
13950
14232
  }
13951
14233
  };
13952
14234
  try {
13953
- const nodeModules = join15(cwd, "node_modules");
13954
- if (existsSync12(nodeModules)) addRoot("node_modules", nodeModules);
14235
+ const nodeModules = join16(cwd, "node_modules");
14236
+ if (existsSync13(nodeModules)) addRoot("node_modules", nodeModules);
13955
14237
  else probes.push({ kind: "node_modules", path: nodeModules });
13956
14238
  const token = firstExecutable(command);
13957
14239
  const executable = resolveExecutable(token, cwd, env);
13958
14240
  let venv = null;
13959
14241
  if (env.VIRTUAL_ENV?.trim()) {
13960
- if (!isAbsolute9(env.VIRTUAL_ENV)) {
14242
+ if (!isAbsolute10(env.VIRTUAL_ENV)) {
13961
14243
  throw new Unattestable("VIRTUAL_ENV is relative (" + JSON.stringify(env.VIRTUAL_ENV) + ")");
13962
14244
  }
13963
- venv = resolve5(env.VIRTUAL_ENV);
14245
+ venv = resolve6(env.VIRTUAL_ENV);
13964
14246
  } else {
13965
14247
  venv = inferredVenv(executable) ?? inferredVenvFromToken(token, cwd);
13966
- if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync12(join15(cwd, ".venv", "pyvenv.cfg"))) {
13967
- venv = join15(cwd, ".venv");
14248
+ if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync13(join16(cwd, ".venv", "pyvenv.cfg"))) {
14249
+ venv = join16(cwd, ".venv");
13968
14250
  }
13969
14251
  }
13970
14252
  if (venv) {
@@ -14000,7 +14282,7 @@ function checkDependencyEnvironment(cwdInput, descriptor) {
14000
14282
  const fingerprint2 = measuredSnapshot(
14001
14283
  descriptor.roots,
14002
14284
  descriptor.probes,
14003
- resolve5(cwdInput),
14285
+ resolve6(cwdInput),
14004
14286
  metricsFor(descriptor)
14005
14287
  );
14006
14288
  if (fingerprint2 !== descriptor.fingerprint) {
@@ -14013,7 +14295,7 @@ function checkDependencyEnvironment(cwdInput, descriptor) {
14013
14295
  }
14014
14296
  function attestDependencyEnvironment(cwdInput, descriptor) {
14015
14297
  const attestation = {
14016
- cwd: resolve5(cwdInput),
14298
+ cwd: resolve6(cwdInput),
14017
14299
  descriptor,
14018
14300
  check: checkDependencyEnvironment(cwdInput, descriptor)
14019
14301
  };
@@ -14021,7 +14303,7 @@ function attestDependencyEnvironment(cwdInput, descriptor) {
14021
14303
  return attestation;
14022
14304
  }
14023
14305
  function reuseDependencyEnvironmentAttestation(cwdInput, descriptor, attestation) {
14024
- if (!attestation || !issuedAttestations.has(attestation) || attestation.descriptor !== descriptor || attestation.cwd !== resolve5(cwdInput)) return null;
14306
+ if (!attestation || !issuedAttestations.has(attestation) || attestation.descriptor !== descriptor || attestation.cwd !== resolve6(cwdInput)) return null;
14025
14307
  metricsFor(descriptor).reusedSnapshots += 1;
14026
14308
  return attestation.check;
14027
14309
  }
@@ -14070,7 +14352,7 @@ var init_dependency_env = __esm({
14070
14352
  import { spawnSync } from "node:child_process";
14071
14353
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
14072
14354
  import { tmpdir as tmpdir3 } from "node:os";
14073
- import { join as join16 } from "node:path";
14355
+ import { join as join17 } from "node:path";
14074
14356
  function emptySuiteDiagnostics() {
14075
14357
  return {
14076
14358
  stdout: { ...EMPTY_STREAM },
@@ -14149,8 +14431,8 @@ function parseCapturedSupervisorResult(stdoutText) {
14149
14431
  };
14150
14432
  }
14151
14433
  function runCapturedProcessSync(executable, args, opts) {
14152
- const stateDir = mkdtempSync2(join16(tmpdir3(), "tw-suite-capture-"));
14153
- const configFile = join16(stateDir, "config.json");
14434
+ const stateDir = mkdtempSync2(join17(tmpdir3(), "tw-suite-capture-"));
14435
+ const configFile = join17(stateDir, "config.json");
14154
14436
  const backstop = opts.backstopMs ?? 3e4;
14155
14437
  try {
14156
14438
  writeFileSync8(
@@ -14385,10 +14667,10 @@ if (child) {
14385
14667
  });
14386
14668
 
14387
14669
  // src/verifier-backend.ts
14388
- import { createHash as createHash9, randomUUID } from "node:crypto";
14670
+ import { createHash as createHash10, randomUUID } from "node:crypto";
14389
14671
  import { spawnSync as spawnSync2 } from "node:child_process";
14390
- import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync15, realpathSync as realpathSync6, statSync as statSync4 } from "node:fs";
14391
- import { delimiter as delimiter2, dirname as dirname6, join as join17, resolve as resolve6 } from "node:path";
14672
+ import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync16, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
14673
+ import { delimiter as delimiter2, dirname as dirname7, join as join18, resolve as resolve7 } from "node:path";
14392
14674
  function identity() {
14393
14675
  const uid = typeof process.getuid === "function" ? process.getuid() : null;
14394
14676
  const groups = typeof process.getgroups === "function" ? process.getgroups() : [];
@@ -14408,12 +14690,12 @@ function writableByCaller(path) {
14408
14690
  }
14409
14691
  }
14410
14692
  function replaceableByCaller(path) {
14411
- let current = resolve6(path);
14693
+ let current = resolve7(path);
14412
14694
  if (writableByCaller(current)) return true;
14413
- current = dirname6(current);
14695
+ current = dirname7(current);
14414
14696
  while (true) {
14415
14697
  if (writableByCaller(current)) return true;
14416
- const parent = dirname6(current);
14698
+ const parent = dirname7(current);
14417
14699
  if (parent === current) break;
14418
14700
  current = parent;
14419
14701
  }
@@ -14422,17 +14704,17 @@ function replaceableByCaller(path) {
14422
14704
  function findExecutable(name) {
14423
14705
  for (const raw of (process.env.PATH ?? "").split(delimiter2)) {
14424
14706
  if (!raw) continue;
14425
- const candidate = join17(raw, name);
14707
+ const candidate = join18(raw, name);
14426
14708
  try {
14427
14709
  accessSync2(candidate, constants3.X_OK);
14428
- return realpathSync6(candidate);
14710
+ return realpathSync7(candidate);
14429
14711
  } catch {
14430
14712
  }
14431
14713
  }
14432
14714
  return null;
14433
14715
  }
14434
14716
  function fileSha256(path) {
14435
- return createHash9("sha256").update(readFileSync15(path)).digest("hex");
14717
+ return createHash10("sha256").update(readFileSync16(path)).digest("hex");
14436
14718
  }
14437
14719
  function engineClientEnv() {
14438
14720
  return {
@@ -14500,7 +14782,7 @@ function inspectImageMetadata(enginePath, host, image) {
14500
14782
  function engineStable(backend) {
14501
14783
  if (!backend.enginePath || !backend.engineSha256) return false;
14502
14784
  try {
14503
- if (realpathSync6(backend.enginePath) !== backend.enginePath) return false;
14785
+ if (realpathSync7(backend.enginePath) !== backend.enginePath) return false;
14504
14786
  if (replaceableByCaller(backend.enginePath)) return false;
14505
14787
  return fileSha256(backend.enginePath) === backend.engineSha256;
14506
14788
  } catch {
@@ -14754,7 +15036,7 @@ function runContainerStage(backend, dir, command, budgetSecs) {
14754
15036
  const args = containerRunArgs({
14755
15037
  image: backend.image,
14756
15038
  name,
14757
- workspace: resolve6(dir),
15039
+ workspace: resolve7(dir),
14758
15040
  command,
14759
15041
  uid,
14760
15042
  gid,
@@ -14765,7 +15047,7 @@ function runContainerStage(backend, dir, command, budgetSecs) {
14765
15047
  backend.enginePath,
14766
15048
  dockerArgs(backend.daemonHost, args),
14767
15049
  {
14768
- cwd: dirname6(backend.enginePath),
15050
+ cwd: dirname7(backend.enginePath),
14769
15051
  env: engineClientEnv(),
14770
15052
  timeoutMs: budgetSecs * 1e3,
14771
15053
  backstopMs: 3e4
@@ -14865,10 +15147,10 @@ var init_verifier_backend = __esm({
14865
15147
 
14866
15148
  // src/cli/verify.ts
14867
15149
  import { execFileSync as execFileSync8, spawnSync as spawnSync3 } from "node:child_process";
14868
- import { createHash as createHash10 } from "node:crypto";
14869
- import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync7, symlinkSync, writeFileSync as writeFileSync9, existsSync as existsSync13 } from "node:fs";
15150
+ import { createHash as createHash11 } from "node:crypto";
15151
+ import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, readFileSync as readFileSync17, readlinkSync as readlinkSync3, rmSync as rmSync7, symlinkSync, writeFileSync as writeFileSync9, existsSync as existsSync14 } from "node:fs";
14870
15152
  import { tmpdir as tmpdir4 } from "node:os";
14871
- import { dirname as dirname7, isAbsolute as isAbsolute10, join as join18, relative as relative5, resolve as resolve7, sep as sep3 } from "node:path";
15153
+ import { dirname as dirname8, isAbsolute as isAbsolute11, join as join19, relative as relative5, resolve as resolve8, sep as sep3 } from "node:path";
14872
15154
  function oracleAssuranceReport() {
14873
15155
  return {
14874
15156
  level: "suite-exit-only",
@@ -14894,8 +15176,8 @@ function baseIsAncestorOfHead(base, cwd) {
14894
15176
  return r.status === 0;
14895
15177
  }
14896
15178
  function inside2(root, path) {
14897
- const rel = relative5(resolve7(root), resolve7(path));
14898
- return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute10(rel);
15179
+ const rel = relative5(resolve8(root), resolve8(path));
15180
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute11(rel);
14899
15181
  }
14900
15182
  function linkParts(path) {
14901
15183
  const parts = sep3 === "\\" ? path.split(/[\\/]+/) : path.split("/");
@@ -14908,10 +15190,10 @@ function driveRelativeLinkTarget(target) {
14908
15190
  return sep3 === "\\" && /^[A-Za-z]:(?![\\/])/.test(target);
14909
15191
  }
14910
15192
  function rootedLinkTarget(target) {
14911
- return isAbsolute10(target) || driveRelativeLinkTarget(target);
15193
+ return isAbsolute11(target) || driveRelativeLinkTarget(target);
14912
15194
  }
14913
15195
  function safeSymlink(target, out3, root, label) {
14914
- if (rootedLinkTarget(target) || !inside2(root, resolve7(dirname7(out3), target))) {
15196
+ if (rootedLinkTarget(target) || !inside2(root, resolve8(dirname8(out3), target))) {
14915
15197
  throw linkEscape(label, target);
14916
15198
  }
14917
15199
  rmSync7(out3, { force: true });
@@ -14919,7 +15201,7 @@ function safeSymlink(target, out3, root, label) {
14919
15201
  }
14920
15202
  function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
14921
15203
  let domain = "tree";
14922
- let stack = linkParts(relative5(root, dirname7(out3)));
15204
+ let stack = linkParts(relative5(root, dirname8(out3)));
14923
15205
  const pending = linkParts(target);
14924
15206
  const seen = /* @__PURE__ */ new Set();
14925
15207
  let hops = 0;
@@ -14936,7 +15218,7 @@ function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
14936
15218
  continue;
14937
15219
  }
14938
15220
  const base = domain === "deps" && dependencyRoot ? dependencyRoot : root;
14939
- const current = join18(base, ...stack);
15221
+ const current = join19(base, ...stack);
14940
15222
  let st;
14941
15223
  try {
14942
15224
  st = lstatSync9(current);
@@ -14957,12 +15239,12 @@ function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
14957
15239
  if (driveRelativeLinkTarget(next)) {
14958
15240
  throw linkEscape(label, target);
14959
15241
  }
14960
- if (isAbsolute10(next)) {
15242
+ if (isAbsolute11(next)) {
14961
15243
  if (domain === "tree" || !dependencyRoot || !inside2(dependencyRoot, next)) {
14962
15244
  throw linkEscape(label, target);
14963
15245
  }
14964
15246
  stack = [];
14965
- pending.unshift(...linkParts(relative5(dependencyRoot, resolve7(next))));
15247
+ pending.unshift(...linkParts(relative5(dependencyRoot, resolve8(next))));
14966
15248
  } else {
14967
15249
  pending.unshift(...linkParts(next));
14968
15250
  }
@@ -14972,7 +15254,7 @@ function rejectLinkedParent(cwd, rel) {
14972
15254
  const parts = rel.split("/").slice(0, -1);
14973
15255
  let current = cwd;
14974
15256
  for (const part of parts) {
14975
- current = join18(current, part);
15257
+ current = join19(current, part);
14976
15258
  let st;
14977
15259
  try {
14978
15260
  st = lstatSync9(current);
@@ -14988,7 +15270,7 @@ function materialize(cwd, dest, dependencyRoot) {
14988
15270
  const listed = workingTreePaths(cwd);
14989
15271
  const links = [];
14990
15272
  for (const rel of listed) {
14991
- const src = join18(cwd, rel);
15273
+ const src = join19(cwd, rel);
14992
15274
  rejectLinkedParent(cwd, rel);
14993
15275
  let st;
14994
15276
  try {
@@ -14996,8 +15278,8 @@ function materialize(cwd, dest, dependencyRoot) {
14996
15278
  } catch {
14997
15279
  continue;
14998
15280
  }
14999
- const out3 = join18(dest, rel);
15000
- mkdirSync7(dirname7(out3), { recursive: true });
15281
+ const out3 = join19(dest, rel);
15282
+ mkdirSync8(dirname8(out3), { recursive: true });
15001
15283
  dropSymlink(out3);
15002
15284
  if (st.isSymbolicLink()) {
15003
15285
  const target = readlinkSync3(src);
@@ -15013,17 +15295,17 @@ function materialize(cwd, dest, dependencyRoot) {
15013
15295
  for (const link of links) {
15014
15296
  validateSymlinkGraph(link.target, link.out, dest, dependencyRoot, link.rel);
15015
15297
  }
15016
- if (dependencyRoot) symlinkSync(dependencyRoot, join18(dest, "node_modules"), "dir");
15298
+ if (dependencyRoot) symlinkSync(dependencyRoot, join19(dest, "node_modules"), "dir");
15017
15299
  }
15018
15300
  function workingTreePaths(cwd) {
15019
15301
  return git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
15020
15302
  }
15021
15303
  function probeFilesystemCaseSensitivity(dir) {
15022
- const upper = join18(dir, ".tw-case-probe-A");
15023
- const lower = join18(dir, ".tw-case-probe-a");
15304
+ const upper = join19(dir, ".tw-case-probe-A");
15305
+ const lower = join19(dir, ".tw-case-probe-a");
15024
15306
  try {
15025
15307
  writeFileSync9(upper, "", { flag: "wx" });
15026
- return !existsSync13(lower);
15308
+ return !existsSync14(lower);
15027
15309
  } finally {
15028
15310
  rmSync7(upper, { force: true });
15029
15311
  }
@@ -15079,15 +15361,15 @@ function baseEntries(base, cwd) {
15079
15361
  });
15080
15362
  }
15081
15363
  function overlayDigest(dest, paths) {
15082
- const h = createHash10("sha256");
15364
+ const h = createHash11("sha256");
15083
15365
  for (const rel of [...paths].sort()) {
15084
15366
  h.update(rel);
15085
15367
  h.update("\0");
15086
- const p = join18(dest, rel);
15368
+ const p = join19(dest, rel);
15087
15369
  try {
15088
15370
  const st = lstatSync9(p);
15089
15371
  h.update(String(st.mode));
15090
- h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync16(p));
15372
+ h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync17(p));
15091
15373
  } catch {
15092
15374
  h.update("<gone>");
15093
15375
  }
@@ -15117,8 +15399,8 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
15117
15399
  throw new Error(`the base's ${e.path} is a submodule; the pristine copy cannot reproduce it`);
15118
15400
  }
15119
15401
  const content = execFileSync8("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
15120
- const out3 = join18(dest, e.path);
15121
- mkdirSync7(dirname7(out3), { recursive: true });
15402
+ const out3 = join19(dest, e.path);
15403
+ mkdirSync8(dirname8(out3), { recursive: true });
15122
15404
  rmSync7(out3, { force: true });
15123
15405
  if (e.mode === "120000") {
15124
15406
  const target = content.toString("utf8");
@@ -15134,7 +15416,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
15134
15416
  const inCopy = workingTreePaths(cwd);
15135
15417
  for (const rel of inCopy) {
15136
15418
  if (!isOverlay(rel) || baseProtected.has(fold(rel))) continue;
15137
- rmSync7(join18(dest, rel), { force: true });
15419
+ rmSync7(join19(dest, rel), { force: true });
15138
15420
  removed++;
15139
15421
  }
15140
15422
  for (const link of restoredLinks) {
@@ -15148,8 +15430,8 @@ function suiteEnv(scratch) {
15148
15430
  if (DROPPED_ENV.has(k) || PINNED_NPM.test(k)) continue;
15149
15431
  env[k] = v;
15150
15432
  }
15151
- const userRc = join18(scratch, "npmrc-user");
15152
- const globalRc = join18(scratch, "npmrc-global");
15433
+ const userRc = join19(scratch, "npmrc-user");
15434
+ const globalRc = join19(scratch, "npmrc-global");
15153
15435
  for (const f of [userRc, globalRc]) writeFileSync9(f, "", { mode: 292 });
15154
15436
  env.npm_config_userconfig = userRc;
15155
15437
  env.npm_config_globalconfig = globalRc;
@@ -15181,7 +15463,7 @@ function runLocalSuite(dir, cmd, budgetSecs) {
15181
15463
  reason: `checkpointed-local verifier is unsupported on ${process.platform}; no trusted local shell contract is defined`
15182
15464
  };
15183
15465
  }
15184
- const scratch = mkdtempSync3(join18(tmpdir4(), "tw-verify-run-"));
15466
+ const scratch = mkdtempSync3(join19(tmpdir4(), "tw-verify-run-"));
15185
15467
  try {
15186
15468
  const r = runCapturedProcessSync(shell.executable, shell.args, {
15187
15469
  cwd: dir,
@@ -15370,8 +15652,8 @@ function runVerify(opts) {
15370
15652
  } catch {
15371
15653
  return cannotVerify("BASE_UNRESOLVABLE", `cannot resolve base rev "${opts.base ?? "HEAD"}"`);
15372
15654
  }
15373
- const visRoot = mkdtempSync3(join18(tmpdir4(), "tw-verify-vis-"));
15374
- const visDir = join18(visRoot, "t");
15655
+ const visRoot = mkdtempSync3(join19(tmpdir4(), "tw-verify-vis-"));
15656
+ const visDir = join19(visRoot, "t");
15375
15657
  const cleanup = (dirs) => {
15376
15658
  if (opts.keep) return;
15377
15659
  for (const d of dirs) rmSync7(d, { recursive: true, force: true });
@@ -15380,7 +15662,7 @@ function runVerify(opts) {
15380
15662
  if (!filesystemCaseSensitive) {
15381
15663
  const present = (rel) => {
15382
15664
  try {
15383
- lstatSync9(join18(cwd, rel));
15665
+ lstatSync9(join19(cwd, rel));
15384
15666
  return true;
15385
15667
  } catch {
15386
15668
  return false;
@@ -15412,7 +15694,7 @@ function runVerify(opts) {
15412
15694
  );
15413
15695
  }
15414
15696
  try {
15415
- mkdirSync7(visDir);
15697
+ mkdirSync8(visDir);
15416
15698
  materialize(cwd, visDir, frozenNodeModules);
15417
15699
  } catch (e) {
15418
15700
  cleanup([visRoot]);
@@ -15466,12 +15748,12 @@ function runVerify(opts) {
15466
15748
  { stage: "visible", dependency_environment: dependencyReport(), verifier_backend: backendReport(), oracle_assurance: oracleAssuranceReport() }
15467
15749
  );
15468
15750
  }
15469
- const priRoot = mkdtempSync3(join18(tmpdir4(), "tw-verify-pri-"));
15470
- const priDir = join18(priRoot, "t");
15751
+ const priRoot = mkdtempSync3(join19(tmpdir4(), "tw-verify-pri-"));
15752
+ const priDir = join19(priRoot, "t");
15471
15753
  let restored = [];
15472
15754
  let removedAdded = 0;
15473
15755
  try {
15474
- mkdirSync7(priDir);
15756
+ mkdirSync8(priDir);
15475
15757
  materialize(cwd, priDir, frozenNodeModules);
15476
15758
  ({ restored, removed: removedAdded } = overlayPristine(
15477
15759
  cwd,
@@ -15740,10 +16022,10 @@ var init_verify = __esm({
15740
16022
 
15741
16023
  // src/cli/run.ts
15742
16024
  import { execFileSync as execFileSync9, spawn, spawnSync as spawnSync4 } from "node:child_process";
15743
- import { randomBytes as randomBytes2 } from "node:crypto";
15744
- import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync10 } from "node:fs";
16025
+ import { randomBytes as randomBytes3 } from "node:crypto";
16026
+ import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync18, readlinkSync as readlinkSync4, realpathSync as realpathSync9, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync10 } from "node:fs";
15745
16027
  import { tmpdir as tmpdir5 } from "node:os";
15746
- import { dirname as dirname8, join as join19, resolve as resolve8 } from "node:path";
16028
+ import { dirname as dirname9, join as join20, resolve as resolve9 } from "node:path";
15747
16029
  function authoritativeRunLifecyclePlatform(platform = process.platform) {
15748
16030
  return platform === "linux";
15749
16031
  }
@@ -15780,7 +16062,7 @@ function writableByCaller2(path) {
15780
16062
  return true;
15781
16063
  } catch {
15782
16064
  }
15783
- const parent = dirname8(cur);
16065
+ const parent = dirname9(cur);
15784
16066
  if (parent === cur) break;
15785
16067
  cur = parent;
15786
16068
  }
@@ -15796,7 +16078,7 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
15796
16078
  }
15797
16079
  for (const candidate of candidates) {
15798
16080
  try {
15799
- const real = realpathSync8(candidate);
16081
+ const real = realpathSync9(candidate);
15800
16082
  const st = statSync5(real);
15801
16083
  if (!st.isFile() || writableByCaller2(real)) continue;
15802
16084
  const probe = spawnSync4(
@@ -15819,9 +16101,9 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
15819
16101
  };
15820
16102
  }
15821
16103
  function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecycleTestMode, machineMode = false) {
15822
- const stateDir = mkdtempSync4(join19(tmpdir5(), "tw-agent-supervisor-"));
15823
- const resultFile = join19(stateDir, "result.json");
15824
- const agentEnvFile = join19(stateDir, "agent-env.json");
16104
+ const stateDir = mkdtempSync4(join20(tmpdir5(), "tw-agent-supervisor-"));
16105
+ const resultFile = join20(stateDir, "result.json");
16106
+ const agentEnvFile = join20(stateDir, "agent-env.json");
15825
16107
  try {
15826
16108
  const linux = process.platform === "linux";
15827
16109
  let executable;
@@ -15895,7 +16177,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
15895
16177
  }
15896
16178
  let state = {};
15897
16179
  try {
15898
- state = JSON.parse(readFileSync17(resultFile, "utf8"));
16180
+ state = JSON.parse(readFileSync18(resultFile, "utf8"));
15899
16181
  } catch {
15900
16182
  return {
15901
16183
  exit: 1,
@@ -15939,7 +16221,7 @@ function observerExited(pid) {
15939
16221
  if (!pidAlive3(pid)) return true;
15940
16222
  if (process.platform !== "linux") return false;
15941
16223
  try {
15942
- const stat = readFileSync17(`/proc/${pid}/stat`, "utf8");
16224
+ const stat = readFileSync18(`/proc/${pid}/stat`, "utf8");
15943
16225
  const state = stat.slice(stat.lastIndexOf(")") + 2).split(" ")[0];
15944
16226
  return state === "Z" || state === "X";
15945
16227
  } catch (e) {
@@ -15947,11 +16229,11 @@ function observerExited(pid) {
15947
16229
  }
15948
16230
  }
15949
16231
  function supervisedObserverLog(cwd) {
15950
- const gd = gitDir(cwd) ?? join19(cwd, ".git");
15951
- return join19(
16232
+ const gd = gitDir(cwd) ?? join20(cwd, ".git");
16233
+ return join20(
15952
16234
  gd,
15953
16235
  "tamperward",
15954
- `run-observer-${process.pid}-${Date.now()}-${randomBytes2(4).toString("hex")}.jsonl`
16236
+ `run-observer-${process.pid}-${Date.now()}-${randomBytes3(4).toString("hex")}.jsonl`
15955
16237
  );
15956
16238
  }
15957
16239
  function startSupervisedObserver(cwd, base, entry) {
@@ -15962,7 +16244,7 @@ function startSupervisedObserver(cwd, base, entry) {
15962
16244
  try {
15963
16245
  child = spawn(
15964
16246
  process.execPath,
15965
- [resolve8(cliEntry), "watch", "--dir", cwd, "--log", log, "--base", base],
16247
+ [resolve9(cliEntry), "watch", "--dir", cwd, "--log", log, "--base", base],
15966
16248
  {
15967
16249
  cwd,
15968
16250
  stdio: ["ignore", "ignore", "inherit"],
@@ -16061,7 +16343,7 @@ function git3(args, cwd) {
16061
16343
  }
16062
16344
  function nowTicks() {
16063
16345
  try {
16064
- return Math.floor(parseFloat(readFileSync17("/proc/uptime", "utf8").split(" ")[0]) * 100);
16346
+ return Math.floor(parseFloat(readFileSync18("/proc/uptime", "utf8").split(" ")[0]) * 100);
16065
16347
  } catch {
16066
16348
  return Number.POSITIVE_INFINITY;
16067
16349
  }
@@ -16070,7 +16352,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
16070
16352
  const out3 = [];
16071
16353
  let real;
16072
16354
  try {
16073
- real = realpathSync8(cwd);
16355
+ real = realpathSync9(cwd);
16074
16356
  } catch {
16075
16357
  return out3;
16076
16358
  }
@@ -16084,7 +16366,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
16084
16366
  const n = Number(pid);
16085
16367
  if (n === process.pid) continue;
16086
16368
  try {
16087
- const stat = readFileSync17(`/proc/${pid}/stat`, "utf8");
16369
+ const stat = readFileSync18(`/proc/${pid}/stat`, "utf8");
16088
16370
  const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
16089
16371
  if (!Number.isFinite(startTicks) || startTicks < spawnedAfterTicks) continue;
16090
16372
  } catch {
@@ -16099,7 +16381,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
16099
16381
  for (const link of procLinks) {
16100
16382
  try {
16101
16383
  const raw = readlinkSync4(link).replace(/ \(deleted\)$/, "");
16102
- const held = raw.startsWith("/") ? resolve8(raw) : raw;
16384
+ const held = raw.startsWith("/") ? resolve9(raw) : raw;
16103
16385
  if (held === real || held.startsWith(real + "/")) {
16104
16386
  holds = true;
16105
16387
  break;
@@ -16112,7 +16394,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
16112
16394
  return out3;
16113
16395
  }
16114
16396
  function runEnvelope(opts) {
16115
- const cwd = resolve8(opts.cwd ?? process.cwd());
16397
+ const cwd = resolve9(opts.cwd ?? process.cwd());
16116
16398
  const say = opts.json ? (_s) => {
16117
16399
  } : out;
16118
16400
  if (!opts.argv.length) {
@@ -16656,8 +16938,8 @@ raise SystemExit(0)
16656
16938
 
16657
16939
  // src/cli/doctor.ts
16658
16940
  import { execFileSync as execFileSync10 } from "node:child_process";
16659
- import { existsSync as existsSync14, readFileSync as readFileSync18, readdirSync as readdirSync5 } from "node:fs";
16660
- import { join as join20, resolve as resolve9 } from "node:path";
16941
+ import { existsSync as existsSync15, readFileSync as readFileSync19, readdirSync as readdirSync5 } from "node:fs";
16942
+ import { join as join21, resolve as resolve10 } from "node:path";
16661
16943
  function lifecyclePlatformCheck(platform = process.platform, linuxPython = platform === "linux" ? trustedLinuxPython() : null) {
16662
16944
  if (platform === "linux") {
16663
16945
  if (linuxPython?.path) {
@@ -16712,10 +16994,10 @@ function wiringState(status, authority) {
16712
16994
  return authority ? "BROKEN" : "WARN";
16713
16995
  }
16714
16996
  function pinsInFile(path) {
16715
- if (!existsSync14(path)) return [];
16997
+ if (!existsSync15(path)) return [];
16716
16998
  let src = "";
16717
16999
  try {
16718
- src = readFileSync18(path, "utf8");
17000
+ src = readFileSync19(path, "utf8");
16719
17001
  } catch {
16720
17002
  return [];
16721
17003
  }
@@ -16735,14 +17017,14 @@ function workflowPermissionCheck(cwd, workflowRels = [".github/workflows/tamperw
16735
17017
  const broken = [];
16736
17018
  const warnings = [];
16737
17019
  for (const rel of workflowRels) {
16738
- const path = resolve9(cwd, rel);
16739
- if (!existsSync14(path)) {
17020
+ const path = resolve10(cwd, rel);
17021
+ if (!existsSync15(path)) {
16740
17022
  broken.push(`${rel} is missing`);
16741
17023
  continue;
16742
17024
  }
16743
17025
  let doc;
16744
17026
  try {
16745
- doc = yaml.parse(readFileSync18(path, "utf8"));
17027
+ doc = yaml.parse(readFileSync19(path, "utf8"));
16746
17028
  } catch (e) {
16747
17029
  broken.push(`${rel} is not valid YAML (${e instanceof Error ? e.message : String(e)})`);
16748
17030
  continue;
@@ -16855,9 +17137,9 @@ function collectLocalPosture(cwd, policy, authorityWorkflows, explicitWorkflow =
16855
17137
  addWiring("codeowners", "codeowners", true);
16856
17138
  checks.push(workflowPermissionCheck(cwd, authorityWorkflows));
16857
17139
  const pins = [
16858
- ...pinsInFile(join20(cwd, ".claude", "settings.json")),
16859
- ...pinsInFile(join20(cwd, ".git", "hooks", "pre-commit")),
16860
- ...pinsInFile(join20(cwd, ".husky", "pre-commit"))
17140
+ ...pinsInFile(join21(cwd, ".claude", "settings.json")),
17141
+ ...pinsInFile(join21(cwd, ".git", "hooks", "pre-commit")),
17142
+ ...pinsInFile(join21(cwd, ".husky", "pre-commit"))
16861
17143
  ];
16862
17144
  const below = pins.filter((pin) => (compareVersions(pin, TW_VERSION) ?? 0) < 0);
16863
17145
  const above = pins.filter((pin) => (compareVersions(TW_VERSION, pin) ?? 0) < 0);
@@ -17082,7 +17364,7 @@ function verifyJobs(doc) {
17082
17364
  return found;
17083
17365
  }
17084
17366
  function diagnose(opts = {}) {
17085
- const cwd = repoRoot(resolve9(opts.cwd ?? process.cwd()));
17367
+ const cwd = repoRoot(resolve10(opts.cwd ?? process.cwd()));
17086
17368
  let policy;
17087
17369
  try {
17088
17370
  policy = policyFor(opts, cwd);
@@ -17106,8 +17388,8 @@ function diagnose(opts = {}) {
17106
17388
  workflowRels.push(opts.workflow);
17107
17389
  } else {
17108
17390
  const workflowDirRel = ".github/workflows";
17109
- const workflowDir = resolve9(cwd, workflowDirRel);
17110
- if (!existsSync14(workflowDir)) {
17391
+ const workflowDir = resolve10(cwd, workflowDirRel);
17392
+ if (!existsSync15(workflowDir)) {
17111
17393
  return fail("ci-verifier", `${workflowDirRel}: workflow directory does not exist`);
17112
17394
  }
17113
17395
  let entries;
@@ -17119,20 +17401,20 @@ function diagnose(opts = {}) {
17119
17401
  `${workflowDirRel}: could not enumerate workflows (${e instanceof Error ? e.message : String(e)})`
17120
17402
  );
17121
17403
  }
17122
- for (const name of entries) workflowRels.push(join20(workflowDirRel, name));
17404
+ for (const name of entries) workflowRels.push(join21(workflowDirRel, name));
17123
17405
  }
17124
17406
  const requiredSecs = requiredVerifierAuthoritySeconds(policy.verify.budget);
17125
17407
  const requiredMinutes = Math.ceil(requiredSecs / 60);
17126
17408
  let verifyJobCount = 0;
17127
17409
  const authorityWorkflows = [];
17128
17410
  for (const workflowRel of workflowRels) {
17129
- const workflowPath = resolve9(cwd, workflowRel);
17130
- if (!existsSync14(workflowPath)) {
17411
+ const workflowPath = resolve10(cwd, workflowRel);
17412
+ if (!existsSync15(workflowPath)) {
17131
17413
  return fail("ci-verifier", `${workflowRel}: workflow does not exist`);
17132
17414
  }
17133
17415
  let doc;
17134
17416
  try {
17135
- doc = yaml.parse(readFileSync18(workflowPath, "utf8"));
17417
+ doc = yaml.parse(readFileSync19(workflowPath, "utf8"));
17136
17418
  } catch (e) {
17137
17419
  return fail(
17138
17420
  "ci-verifier",
@@ -17288,16 +17570,16 @@ var init_doctor = __esm({
17288
17570
  // src/cli/trace-verify.ts
17289
17571
  import { execFileSync as execFileSync11, spawnSync as spawnSync5 } from "node:child_process";
17290
17572
  import {
17291
- existsSync as existsSync15,
17573
+ existsSync as existsSync16,
17292
17574
  mkdtempSync as mkdtempSync5,
17293
- readFileSync as readFileSync19,
17575
+ readFileSync as readFileSync20,
17294
17576
  readdirSync as readdirSync6,
17295
- realpathSync as realpathSync9,
17577
+ realpathSync as realpathSync10,
17296
17578
  rmSync as rmSync9,
17297
17579
  symlinkSync as symlinkSync2
17298
17580
  } from "node:fs";
17299
17581
  import { tmpdir as tmpdir6 } from "node:os";
17300
- import { isAbsolute as isAbsolute11, join as join21, relative as relative6, resolve as resolve10, sep as sep4 } from "node:path";
17582
+ import { isAbsolute as isAbsolute12, join as join22, relative as relative6, resolve as resolve11, sep as sep4 } from "node:path";
17301
17583
  function unescapeStraceString(s) {
17302
17584
  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, "\\");
17303
17585
  }
@@ -17322,11 +17604,11 @@ function parseStraceFileAccess(raw) {
17322
17604
  return out3;
17323
17605
  }
17324
17606
  function inside3(root, path) {
17325
- const rel = relative6(resolve10(root), resolve10(path));
17326
- return rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !isAbsolute11(rel);
17607
+ const rel = relative6(resolve11(root), resolve11(path));
17608
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !isAbsolute12(rel);
17327
17609
  }
17328
17610
  function normalRepoPath(root, raw) {
17329
- const abs = isAbsolute11(raw) ? resolve10(raw) : resolve10(root, raw);
17611
+ const abs = isAbsolute12(raw) ? resolve11(raw) : resolve11(root, raw);
17330
17612
  if (!inside3(root, abs)) return null;
17331
17613
  const rel = relative6(root, abs).split(sep4).join("/");
17332
17614
  return rel && rel !== ".." ? rel : null;
@@ -17346,7 +17628,7 @@ function summarizeTraceRuns(opts) {
17346
17628
  for (const item of run) {
17347
17629
  const rel = normalRepoPath(opts.root, item.path);
17348
17630
  const isTracked = rel !== null && opts.tracked.has(rel);
17349
- const keyPath = rel !== null ? opts.tracked.has(rel) ? rel : null : resolve10(opts.root, item.path);
17631
+ const keyPath = rel !== null ? opts.tracked.has(rel) ? rel : null : resolve11(opts.root, item.path);
17350
17632
  if (keyPath === null) continue;
17351
17633
  const target = isTracked ? repo : external;
17352
17634
  const key2 = `${isTracked ? "repo" : "external"}\0${keyPath}`;
@@ -17423,36 +17705,36 @@ function materializeBase(base, cwd, dest) {
17423
17705
  `tar extraction failed${untar.stderr?.length ? `: ${String(untar.stderr).trim()}` : ""}`
17424
17706
  );
17425
17707
  }
17426
- const hostModules = join21(cwd, "node_modules");
17427
- const tracedModules = join21(dest, "node_modules");
17428
- if (existsSync15(hostModules) && !existsSync15(tracedModules)) {
17429
- symlinkSync2(resolve10(hostModules), tracedModules, "dir");
17708
+ const hostModules = join22(cwd, "node_modules");
17709
+ const tracedModules = join22(dest, "node_modules");
17710
+ if (existsSync16(hostModules) && !existsSync16(tracedModules)) {
17711
+ symlinkSync2(resolve11(hostModules), tracedModules, "dir");
17430
17712
  }
17431
17713
  }
17432
17714
  function traceFiles(prefix) {
17433
- const dir = resolve10(prefix, "..");
17715
+ const dir = resolve11(prefix, "..");
17434
17716
  const base = prefix.split(sep4).at(-1) ?? prefix;
17435
- return readdirSync6(dir).filter((name) => name === base || name.startsWith(base + ".")).map((name) => join21(dir, name)).sort();
17717
+ return readdirSync6(dir).filter((name) => name === base || name.startsWith(base + ".")).map((name) => join22(dir, name)).sort();
17436
17718
  }
17437
17719
  function rewriteTraceRoot(accesses, actualRoot) {
17438
17720
  return accesses.map((item) => {
17439
- const abs = isAbsolute11(item.path) ? resolve10(item.path) : resolve10(actualRoot, item.path);
17721
+ const abs = isAbsolute12(item.path) ? resolve11(item.path) : resolve11(actualRoot, item.path);
17440
17722
  if (!inside3(actualRoot, abs)) return { ...item, path: abs };
17441
17723
  const rel = relative6(actualRoot, abs).split(sep4).join("/");
17442
17724
  try {
17443
- const real = resolve10(realpathSync9(abs));
17725
+ const real = resolve11(realpathSync10(abs));
17444
17726
  if (!inside3(actualRoot, real)) return { ...item, path: real };
17445
17727
  } catch {
17446
17728
  }
17447
- return { ...item, path: join21(TRACE_ROOT, ...rel.split("/")) };
17729
+ return { ...item, path: join22(TRACE_ROOT, ...rel.split("/")) };
17448
17730
  });
17449
17731
  }
17450
17732
  function runOneTrace(cwd, base, command, budget) {
17451
- const root = mkdtempSync5(join21(tmpdir6(), "tw-trace-base-"));
17452
- const traceDir = mkdtempSync5(join21(tmpdir6(), "tw-trace-log-"));
17733
+ const root = mkdtempSync5(join22(tmpdir6(), "tw-trace-base-"));
17734
+ const traceDir = mkdtempSync5(join22(tmpdir6(), "tw-trace-log-"));
17453
17735
  try {
17454
17736
  materializeBase(base, cwd, root);
17455
- const prefix = join21(traceDir, "trace");
17737
+ const prefix = join22(traceDir, "trace");
17456
17738
  const traced = spawnSync5(
17457
17739
  "strace",
17458
17740
  [
@@ -17485,7 +17767,7 @@ function runOneTrace(cwd, base, command, budget) {
17485
17767
  if (logs.length === 0) {
17486
17768
  throw new Error("strace produced no trace files");
17487
17769
  }
17488
- const accesses = logs.flatMap((path) => parseStraceFileAccess(readFileSync19(path, "utf8")));
17770
+ const accesses = logs.flatMap((path) => parseStraceFileAccess(readFileSync20(path, "utf8")));
17489
17771
  return {
17490
17772
  exit: traced.status ?? 1,
17491
17773
  accesses: rewriteTraceRoot(accesses, root)
@@ -17529,7 +17811,7 @@ function renderText2(report2) {
17529
17811
  for (const note of report2.notes) out3(`note: ${note}`);
17530
17812
  }
17531
17813
  function runTraceVerify(opts = {}) {
17532
- const cwd = resolve10(opts.cwd ?? process.cwd());
17814
+ const cwd = resolve11(opts.cwd ?? process.cwd());
17533
17815
  if (process.platform !== "linux") {
17534
17816
  process.stderr.write("tamperward trace-verify: Linux only in this release; unsupported platform \u2014 no parity is implied.\n");
17535
17817
  return 2;
@@ -17676,9 +17958,9 @@ var init_trace_verify = __esm({
17676
17958
 
17677
17959
  // src/cli/onboard.ts
17678
17960
  import { execFileSync as execFileSync12 } from "node:child_process";
17679
- import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync20, realpathSync as realpathSync10, rmSync as rmSync10 } from "node:fs";
17961
+ import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync21, realpathSync as realpathSync11, rmSync as rmSync10 } from "node:fs";
17680
17962
  import { tmpdir as tmpdir7 } from "node:os";
17681
- import { join as join22, relative as relative7, resolve as resolve11, sep as sep5 } from "node:path";
17963
+ import { join as join23, relative as relative7, resolve as resolve12, sep as sep5 } from "node:path";
17682
17964
  import { createInterface } from "node:readline";
17683
17965
  import { parseDocument } from "yaml";
17684
17966
  function readlineAsker(input, output) {
@@ -17687,9 +17969,9 @@ function readlineAsker(input, output) {
17687
17969
  let pending = null;
17688
17970
  rl.on("close", () => {
17689
17971
  closed = true;
17690
- const resolve16 = pending;
17972
+ const resolve17 = pending;
17691
17973
  pending = null;
17692
- resolve16?.(null);
17974
+ resolve17?.(null);
17693
17975
  });
17694
17976
  rl.on("SIGINT", () => rl.close());
17695
17977
  const ask = (question) => new Promise((resolvePromise) => {
@@ -17715,7 +17997,7 @@ function terminalText(text) {
17715
17997
  return stripControl(text).replace(/\s+/g, " ").trim();
17716
17998
  }
17717
17999
  function committableSetupPath(cwd, path) {
17718
- const rel = relative7(cwd, resolve11(cwd, path));
18000
+ const rel = relative7(cwd, resolve12(cwd, path));
17719
18001
  if (!rel || rel === ".." || rel.startsWith(".." + sep5)) return false;
17720
18002
  return rel !== ".git" && !rel.startsWith(".git" + sep5);
17721
18003
  }
@@ -17746,13 +18028,13 @@ function explainVerdict(v, code2) {
17746
18028
  }
17747
18029
  }
17748
18030
  function writeVerifyCommand(cwd, command) {
17749
- const path = join22(cwd, POLICY_FILE);
18031
+ const path = join23(cwd, POLICY_FILE);
17750
18032
  const kind = writeTargetKind(path);
17751
18033
  if (kind === "symlink" || kind === "irregular") {
17752
18034
  return `${POLICY_FILE} is not a regular file; refusing to follow or replace a symlink or special file`;
17753
18035
  }
17754
18036
  const mode = existingMode(path, 420);
17755
- const original = kind === "file" ? readFileSync20(path, "utf8") : null;
18037
+ const original = kind === "file" ? readFileSync21(path, "utf8") : null;
17756
18038
  let next;
17757
18039
  try {
17758
18040
  const doc = parseDocument(original && original.trim() ? original : "version: 1\n");
@@ -17775,7 +18057,7 @@ function writeVerifyCommand(cwd, command) {
17775
18057
  }
17776
18058
  }
17777
18059
  async function runOnboard(opts, io = {}) {
17778
- const requestedCwd = resolve11(opts.cwd ?? process.cwd());
18060
+ const requestedCwd = resolve12(opts.cwd ?? process.cwd());
17779
18061
  const out3 = io.out ?? ((line) => void process.stdout.write(line + "\n"));
17780
18062
  const errLine = (line) => void process.stderr.write(line + "\n");
17781
18063
  const rawErr = (line) => errLine(terminalText(line));
@@ -17815,10 +18097,10 @@ async function runOnboard(opts, io = {}) {
17815
18097
  fail(requestedCwd + " is not inside a Git repository. Run `git init` first.");
17816
18098
  return 2;
17817
18099
  }
17818
- const cwd = resolve11(rootText);
18100
+ const cwd = resolve12(rootText);
17819
18101
  let sameRoot = cwd === requestedCwd;
17820
18102
  try {
17821
- sameRoot = realpathSync10(cwd) === realpathSync10(requestedCwd);
18103
+ sameRoot = realpathSync11(cwd) === realpathSync11(requestedCwd);
17822
18104
  } catch {
17823
18105
  }
17824
18106
  if (!sameRoot) {
@@ -18141,13 +18423,13 @@ function runDemo(cwd, head, runners, out3) {
18141
18423
  }
18142
18424
  const before = treeFingerprint(cwd);
18143
18425
  out3(`working tree fingerprint before: ${before.slice(0, 16)}`);
18144
- const wt = mkdtempSync6(join22(tmpdir7(), "tw-onboard-demo-"));
18426
+ const wt = mkdtempSync6(join23(tmpdir7(), "tw-onboard-demo-"));
18145
18427
  try {
18146
18428
  if (git5(cwd, ["worktree", "add", "--detach", wt, head]) === null) {
18147
18429
  out3("Demo skipped: could not create a temporary worktree (git worktree add failed).");
18148
18430
  return;
18149
18431
  }
18150
- const targetPath = join22(wt, target.path);
18432
+ const targetPath = join23(wt, target.path);
18151
18433
  const targetStat = lstatSync10(targetPath, { throwIfNoEntry: false });
18152
18434
  if (!targetStat || !targetStat.isFile() || targetStat.isSymbolicLink()) {
18153
18435
  out3("Demo skipped: the selected test is not a regular file in the disposable worktree.");
@@ -18156,8 +18438,8 @@ function runDemo(cwd, head, runners, out3) {
18156
18438
  let worktreeRoot;
18157
18439
  let realTarget;
18158
18440
  try {
18159
- worktreeRoot = realpathSync10(wt);
18160
- realTarget = realpathSync10(targetPath);
18441
+ worktreeRoot = realpathSync11(wt);
18442
+ realTarget = realpathSync11(targetPath);
18161
18443
  } catch {
18162
18444
  out3("Demo skipped: the selected test could not be resolved safely inside the disposable worktree.");
18163
18445
  return;
@@ -18232,7 +18514,7 @@ var init_onboard = __esm({
18232
18514
  });
18233
18515
 
18234
18516
  // src/research/adapter.ts
18235
- import { isAbsolute as isAbsolute12, resolve as resolve12 } from "node:path";
18517
+ import { isAbsolute as isAbsolute13, resolve as resolve13 } from "node:path";
18236
18518
  function taskEnv(task) {
18237
18519
  return {
18238
18520
  TAMPERWARD_RESEARCH_TASK: task.id,
@@ -18248,7 +18530,7 @@ function normalizeCommandArgv(argv, cwd = process.cwd()) {
18248
18530
  throw new ResearchError('the command adapter needs an agent command after "--"');
18249
18531
  }
18250
18532
  const head = argv[0];
18251
- return head.includes("/") && !isAbsolute12(head) ? [resolve12(cwd, head), ...argv.slice(1)] : [...argv];
18533
+ return head.includes("/") && !isAbsolute13(head) ? [resolve13(cwd, head), ...argv.slice(1)] : [...argv];
18252
18534
  }
18253
18535
  function commandAdapter(argv, cwd = process.cwd()) {
18254
18536
  const anchored = normalizeCommandArgv(argv, cwd);
@@ -18347,9 +18629,9 @@ var init_capture = __esm({
18347
18629
  });
18348
18630
 
18349
18631
  // src/research/manifest.ts
18350
- import { createHash as createHash12 } from "node:crypto";
18351
- import { readFileSync as readFileSync21 } from "node:fs";
18352
- import { dirname as dirname9, resolve as resolve13 } from "node:path";
18632
+ import { createHash as createHash13 } from "node:crypto";
18633
+ import { readFileSync as readFileSync22 } from "node:fs";
18634
+ import { dirname as dirname10, resolve as resolve14 } from "node:path";
18353
18635
  function taskFrom(raw, index, manifestDir) {
18354
18636
  const where = `task #${index + 1}`;
18355
18637
  if (!isRecord(raw)) throw new ResearchError(`task manifest: ${where} is not an object`);
@@ -18372,17 +18654,17 @@ function taskFrom(raw, index, manifestDir) {
18372
18654
  const isUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(repo) || /^[^/]+@[^:]+:/.test(repo);
18373
18655
  return {
18374
18656
  id,
18375
- repo: isUrl ? repo : resolve13(manifestDir, repo),
18657
+ repo: isUrl ? repo : resolve14(manifestDir, repo),
18376
18658
  base: base ?? "HEAD",
18377
18659
  prompt,
18378
18660
  verify: { command: verify.command, ...budget !== void 0 ? { budget } : {} }
18379
18661
  };
18380
18662
  }
18381
18663
  function readManifest(path) {
18382
- const abs = resolve13(path);
18664
+ const abs = resolve14(path);
18383
18665
  let bytes;
18384
18666
  try {
18385
- bytes = readFileSync21(abs);
18667
+ bytes = readFileSync22(abs);
18386
18668
  } catch (e) {
18387
18669
  throw new ResearchError(`cannot read task manifest ${abs}: ${e instanceof Error ? e.message : String(e)}`);
18388
18670
  }
@@ -18399,13 +18681,13 @@ function readManifest(path) {
18399
18681
  if (!Array.isArray(raw.tasks) || raw.tasks.length === 0) {
18400
18682
  throw new ResearchError(`task manifest ${abs}: no tasks (tasks must be a non-empty array)`);
18401
18683
  }
18402
- const tasks = raw.tasks.map((t, i) => taskFrom(t, i, dirname9(abs)));
18684
+ const tasks = raw.tasks.map((t, i) => taskFrom(t, i, dirname10(abs)));
18403
18685
  const seen = /* @__PURE__ */ new Set();
18404
18686
  for (const t of tasks) {
18405
18687
  if (seen.has(t.id)) throw new ResearchError(`task manifest ${abs}: duplicate task id "${t.id}"`);
18406
18688
  seen.add(t.id);
18407
18689
  }
18408
- return { path: abs, sha256: createHash12("sha256").update(bytes).digest("hex"), tasks };
18690
+ return { path: abs, sha256: createHash13("sha256").update(bytes).digest("hex"), tasks };
18409
18691
  }
18410
18692
  var MANIFEST_VERSION;
18411
18693
  var init_manifest = __esm({
@@ -18594,18 +18876,18 @@ var init_record = __esm({
18594
18876
 
18595
18877
  // src/research/run.ts
18596
18878
  import { execFileSync as execFileSync13 } from "node:child_process";
18597
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync22, renameSync as renameSync4, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
18598
- import { join as join23, resolve as resolve14 } from "node:path";
18879
+ import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync23, renameSync as renameSync4, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
18880
+ import { join as join24, resolve as resolve15 } from "node:path";
18599
18881
  function git6(args, cwd) {
18600
18882
  return execFileSync13("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
18601
18883
  }
18602
18884
  function pairRecordPath(ledger, task, pair) {
18603
- return join23(ledger, "pairs", `${task}--${pair}.json`);
18885
+ return join24(ledger, "pairs", `${task}--${pair}.json`);
18604
18886
  }
18605
18887
  function resumableRecord(path, expected) {
18606
18888
  let raw;
18607
18889
  try {
18608
- raw = JSON.parse(readFileSync22(path, "utf8"));
18890
+ raw = JSON.parse(readFileSync23(path, "utf8"));
18609
18891
  } catch (e) {
18610
18892
  throw new ResearchError(`ledger record ${path} is malformed (${errorMessage(e).split("\n")[0]}); remove it to regenerate the pair, or use a new --out`);
18611
18893
  }
@@ -18646,9 +18928,9 @@ function writeRecordAtomically(path, text) {
18646
18928
  renameSync4(tmp, path);
18647
18929
  }
18648
18930
  function freshWorkspace(ledger, task, pair, arm, sourceBase) {
18649
- const ws3 = join23(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
18931
+ const ws3 = join24(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
18650
18932
  rmSync11(ws3, { recursive: true, force: true });
18651
- mkdirSync8(join23(ledger, "workspaces"), { recursive: true });
18933
+ mkdirSync9(join24(ledger, "workspaces"), { recursive: true });
18652
18934
  try {
18653
18935
  execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo, ws3], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
18654
18936
  } catch (e) {
@@ -18894,16 +19176,16 @@ function runResearch(opts) {
18894
19176
  err2("tamperward research: the gated arm cannot start here (see `tamperward doctor`); run as a non-root user on Linux.");
18895
19177
  return 2;
18896
19178
  }
18897
- const ledger = resolve14(opts.out);
19179
+ const ledger = resolve15(opts.out);
18898
19180
  const pairs = opts.pairs ?? 1;
18899
- mkdirSync8(join23(ledger, "pairs"), { recursive: true });
19181
+ mkdirSync9(join24(ledger, "pairs"), { recursive: true });
18900
19182
  for (const task of tasks) {
18901
19183
  const existingRecords = /* @__PURE__ */ new Map();
18902
19184
  let sourceBase = null;
18903
19185
  try {
18904
19186
  for (let pair = 1; pair <= pairs; pair++) {
18905
19187
  const path = pairRecordPath(ledger, task.id, pair);
18906
- if (!existsSync16(path)) continue;
19188
+ if (!existsSync17(path)) continue;
18907
19189
  const existing = resumableRecord(path, {
18908
19190
  task: task.id,
18909
19191
  pair,
@@ -19028,10 +19310,10 @@ var init_run2 = __esm({
19028
19310
  });
19029
19311
 
19030
19312
  // src/research/summarize.ts
19031
- import { readdirSync as readdirSync7, readFileSync as readFileSync23 } from "node:fs";
19032
- import { join as join24, resolve as resolve15 } from "node:path";
19313
+ import { readdirSync as readdirSync7, readFileSync as readFileSync24 } from "node:fs";
19314
+ import { join as join25, resolve as resolve16 } from "node:path";
19033
19315
  function readLedger2(dir) {
19034
- const pairsDir = join24(resolve15(dir), "pairs");
19316
+ const pairsDir = join25(resolve16(dir), "pairs");
19035
19317
  let names;
19036
19318
  try {
19037
19319
  names = readdirSync7(pairsDir).filter((n) => n.endsWith(".json")).sort();
@@ -19040,10 +19322,10 @@ function readLedger2(dir) {
19040
19322
  }
19041
19323
  if (names.length === 0) throw new ResearchError(`ledger ${pairsDir} holds no pair records`);
19042
19324
  return names.map((n) => {
19043
- const path = join24(pairsDir, n);
19325
+ const path = join25(pairsDir, n);
19044
19326
  let raw;
19045
19327
  try {
19046
- raw = JSON.parse(readFileSync23(path, "utf8"));
19328
+ raw = JSON.parse(readFileSync24(path, "utf8"));
19047
19329
  } catch (e) {
19048
19330
  throw new ResearchError(`ledger record ${path} is not valid JSON: ${errorMessage(e)}`);
19049
19331
  }
@@ -19284,6 +19566,17 @@ function parseDoctor(args) {
19284
19566
  }
19285
19567
  return o;
19286
19568
  }
19569
+ function parseStats(args) {
19570
+ const o = {};
19571
+ for (let i = 0; i < args.length; i++) {
19572
+ const a = args[i];
19573
+ if (a === "--cwd") o.cwd = args[++i];
19574
+ else if (a === "--file") o.file = args[++i];
19575
+ else if (a === "--since") o.since = args[++i];
19576
+ else if (a === "--json") o.json = true;
19577
+ }
19578
+ return o;
19579
+ }
19287
19580
  function parseCheck(args) {
19288
19581
  const o = {};
19289
19582
  for (let i = 0; i < args.length; i++) {
@@ -19423,6 +19716,16 @@ function validateCliArgs(cmd, args) {
19423
19716
  }
19424
19717
  }).error;
19425
19718
  }
19719
+ if (cmd === "stats") {
19720
+ return validateFlatArgs(args, {
19721
+ flags: ["--json"],
19722
+ values: {
19723
+ "--cwd": "string",
19724
+ "--file": "string",
19725
+ "--since": "string"
19726
+ }
19727
+ }).error;
19728
+ }
19426
19729
  if (cmd === "onboard") {
19427
19730
  const parsed = validateFlatArgs(args, {
19428
19731
  flags: ["--yes", "--skip-demo", "--demo", "--no-github"],
@@ -19583,6 +19886,12 @@ Formats:
19583
19886
  tamperward research summarize --ledger D aggregate measured pairs into model behaviour,
19584
19887
  independent outcome, TamperWard hits/misses
19585
19888
  and paired counts \u2014 no composite score
19889
+ tamperward stats [--file F] [--since 30d] aggregate privacy-safe hook/sweep audit
19890
+ [--json] [--cwd D] events by rule and enforcement surface.
19891
+ Defaults to the repository-local
19892
+ .git/tamperward/audit.jsonl (or
19893
+ TAMPERWARD_AUDIT_LOG). A finding is an
19894
+ integrity signal, not proof of agent intent.
19586
19895
  tamperward allow <rule> --reason "..." record a human sign-off (local audit ledger)
19587
19896
  [--file F] [--cwd D]
19588
19897
  tamperward onboard [--yes] [--cwd D] guided first-run setup: preflight, the
@@ -19653,6 +19962,8 @@ function main(argv) {
19653
19962
  return runInit(parseInit(rest));
19654
19963
  case "doctor":
19655
19964
  return runDoctor(parseDoctor(rest));
19965
+ case "stats":
19966
+ return runStats(parseStats(rest));
19656
19967
  case "onboard":
19657
19968
  return runOnboard(parseOnboard(rest));
19658
19969
  case "watch":
@@ -19714,13 +20025,14 @@ var init_main = __esm({
19714
20025
  init_watch();
19715
20026
  init_onboard();
19716
20027
  init_research();
20028
+ init_audit();
19717
20029
  }
19718
20030
  });
19719
20031
 
19720
20032
  // src/cli/index.ts
19721
20033
  init_hook_client();
19722
20034
  init_exit();
19723
- import { readFileSync as readFileSync24 } from "node:fs";
20035
+ import { readFileSync as readFileSync25 } from "node:fs";
19724
20036
  function loadMain() {
19725
20037
  return Promise.resolve().then(() => (init_main(), main_exports));
19726
20038
  }
@@ -19733,7 +20045,7 @@ async function launch(argv) {
19733
20045
  if (kind && hookServiceEnabled()) {
19734
20046
  let raw = null;
19735
20047
  try {
19736
- raw = readFileSync24(0, "utf8");
20048
+ raw = readFileSync25(0, "utf8");
19737
20049
  } catch {
19738
20050
  raw = null;
19739
20051
  }