tamperward 2.27.0 → 2.29.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/README.md +6 -1
- package/dist/cli/index.js +641 -279
- package/package.json +1 -1
- package/schemas/audit-v1.schema.json +100 -0
- package/schemas/stats-v1.schema.json +119 -0
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((
|
|
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
|
-
|
|
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",
|
|
@@ -3538,15 +3539,15 @@ function buildAliasMap(sf) {
|
|
|
3538
3539
|
ts.forEachChild(node, collect2);
|
|
3539
3540
|
};
|
|
3540
3541
|
collect2(sf);
|
|
3541
|
-
const
|
|
3542
|
+
const resolve17 = (name, seen) => {
|
|
3542
3543
|
if (seen.has(name)) return null;
|
|
3543
3544
|
seen.add(name);
|
|
3544
3545
|
const t = decls.get(name);
|
|
3545
|
-
return t ? kindOfType(t, (n) =>
|
|
3546
|
+
return t ? kindOfType(t, (n) => resolve17(n, seen)) : null;
|
|
3546
3547
|
};
|
|
3547
3548
|
const resolved = /* @__PURE__ */ new Map();
|
|
3548
3549
|
for (const name of decls.keys()) {
|
|
3549
|
-
const k =
|
|
3550
|
+
const k = resolve17(name, /* @__PURE__ */ new Set());
|
|
3550
3551
|
if (k) resolved.set(name, k);
|
|
3551
3552
|
}
|
|
3552
3553
|
return resolved;
|
|
@@ -4933,8 +4934,8 @@ function numericOf(e) {
|
|
|
4933
4934
|
}
|
|
4934
4935
|
return void 0;
|
|
4935
4936
|
}
|
|
4936
|
-
function metricsOf(expr,
|
|
4937
|
-
const obj =
|
|
4937
|
+
function metricsOf(expr, resolve17) {
|
|
4938
|
+
const obj = resolve17(expr);
|
|
4938
4939
|
if (!ts.isObjectLiteralExpression(obj)) return void 0;
|
|
4939
4940
|
const out3 = { values: {}, opaque: false };
|
|
4940
4941
|
let any = false;
|
|
@@ -4948,7 +4949,7 @@ function metricsOf(expr, resolve16) {
|
|
|
4948
4949
|
const k = keyName(p.name);
|
|
4949
4950
|
if (k === null || !isMetric(k)) continue;
|
|
4950
4951
|
any = true;
|
|
4951
|
-
const n = numericOf(
|
|
4952
|
+
const n = numericOf(resolve17(p.initializer));
|
|
4952
4953
|
if (n === void 0) out3.opaque = true;
|
|
4953
4954
|
else out3.values[k] = n;
|
|
4954
4955
|
}
|
|
@@ -4995,17 +4996,17 @@ function parseThresholds(src) {
|
|
|
4995
4996
|
try {
|
|
4996
4997
|
const sf = sourceOf(src);
|
|
4997
4998
|
if (!sf) return res;
|
|
4998
|
-
const
|
|
4999
|
+
const resolve17 = resolverFor(sf);
|
|
4999
5000
|
const visit = (node) => {
|
|
5000
5001
|
if (ts.isPropertyAssignment(node)) {
|
|
5001
5002
|
const key2 = keyName(node.name);
|
|
5002
5003
|
if (key2 === "coverageThreshold" || key2 === "thresholds" && underKey(node, "coverage")) {
|
|
5003
5004
|
res.present = true;
|
|
5004
|
-
const init =
|
|
5005
|
+
const init = resolve17(node.initializer);
|
|
5005
5006
|
if (!ts.isObjectLiteralExpression(init)) {
|
|
5006
5007
|
res.opaque = true;
|
|
5007
5008
|
} else {
|
|
5008
|
-
const flat = metricsOf(init,
|
|
5009
|
+
const flat = metricsOf(init, resolve17);
|
|
5009
5010
|
if (flat) res.global = merge(res.global, flat);
|
|
5010
5011
|
for (const p of init.properties) {
|
|
5011
5012
|
if (ts.isSpreadAssignment(p)) {
|
|
@@ -5019,7 +5020,7 @@ function parseThresholds(src) {
|
|
|
5019
5020
|
res.global = merge(res.global, { values: { branches: 100, functions: 100, lines: 100, statements: 100 }, opaque: false });
|
|
5020
5021
|
continue;
|
|
5021
5022
|
}
|
|
5022
|
-
const m = metricsOf(p.initializer,
|
|
5023
|
+
const m = metricsOf(p.initializer, resolve17);
|
|
5023
5024
|
if (!m) continue;
|
|
5024
5025
|
if (k === "global") res.global = merge(res.global, m);
|
|
5025
5026
|
else res.paths.set(norm(k), m);
|
|
@@ -5053,18 +5054,18 @@ function parseLists(src) {
|
|
|
5053
5054
|
try {
|
|
5054
5055
|
const sf = sourceOf(src);
|
|
5055
5056
|
if (!sf) return res;
|
|
5056
|
-
const
|
|
5057
|
+
const resolve17 = resolverFor(sf);
|
|
5057
5058
|
const visit = (node) => {
|
|
5058
5059
|
if (ts.isPropertyAssignment(node)) {
|
|
5059
5060
|
const k = keyName(node.name);
|
|
5060
5061
|
const vitestCoverage = ownerKey(node) === "coverage";
|
|
5061
5062
|
if (k === "collectCoverageFrom" || k === "include" && vitestCoverage) {
|
|
5062
|
-
const { items, opaque } = literals(
|
|
5063
|
+
const { items, opaque } = literals(resolve17(node.initializer));
|
|
5063
5064
|
res.collectFrom = items;
|
|
5064
5065
|
res.collectKey = k === "include" ? "coverage.include" : k;
|
|
5065
5066
|
if (opaque) res.opaqueCollect = true;
|
|
5066
5067
|
} else if (k === "coveragePathIgnorePatterns" || k === "exclude" && vitestCoverage) {
|
|
5067
|
-
const { items, opaque } = literals(
|
|
5068
|
+
const { items, opaque } = literals(resolve17(node.initializer));
|
|
5068
5069
|
(k === "exclude" ? res.ignore : res.ignoreRe).push(...items);
|
|
5069
5070
|
if (opaque) res.opaqueIgnore = true;
|
|
5070
5071
|
}
|
|
@@ -11829,15 +11830,251 @@ var init_fs_events = __esm({
|
|
|
11829
11830
|
}
|
|
11830
11831
|
});
|
|
11831
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
|
+
|
|
11832
12069
|
// src/cli/hook.ts
|
|
11833
|
-
import { readFileSync as
|
|
11834
|
-
import { createHash as
|
|
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";
|
|
11835
12072
|
function setSnapshotCache(cache3) {
|
|
11836
12073
|
snapshotCache = cache3;
|
|
11837
12074
|
}
|
|
11838
12075
|
function readStdin() {
|
|
11839
12076
|
try {
|
|
11840
|
-
return
|
|
12077
|
+
return readFileSync11(0, "utf8");
|
|
11841
12078
|
} catch (e) {
|
|
11842
12079
|
throw new HookInputError(`cannot read the hook payload on stdin: ${e instanceof Error ? e.message : String(e)}`);
|
|
11843
12080
|
}
|
|
@@ -11865,7 +12102,7 @@ function recordDenylog(blocks2) {
|
|
|
11865
12102
|
const log = process.env.TAMPERWARD_DENYLOG;
|
|
11866
12103
|
if (!log) return;
|
|
11867
12104
|
try {
|
|
11868
|
-
|
|
12105
|
+
appendFileSync5(log, blocks2.map((b) => b.rule).join(",") + "\n");
|
|
11869
12106
|
} catch {
|
|
11870
12107
|
}
|
|
11871
12108
|
}
|
|
@@ -11873,7 +12110,7 @@ function errText2(e) {
|
|
|
11873
12110
|
const m = e instanceof Error ? e.message : String(e);
|
|
11874
12111
|
return m.length > 200 ? m.slice(0, 200) + "\u2026" : m;
|
|
11875
12112
|
}
|
|
11876
|
-
function failClosed(kind, detail) {
|
|
12113
|
+
function failClosed(kind, detail, audit) {
|
|
11877
12114
|
const f = {
|
|
11878
12115
|
rule: "tamperward-unavailable",
|
|
11879
12116
|
severity: "block",
|
|
@@ -11882,7 +12119,7 @@ function failClosed(kind, detail) {
|
|
|
11882
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.",
|
|
11883
12120
|
signoff: { required: true, command: 'tamperward allow --reason "..."' }
|
|
11884
12121
|
};
|
|
11885
|
-
return verdict([f], kind);
|
|
12122
|
+
return verdict([f], kind, audit);
|
|
11886
12123
|
}
|
|
11887
12124
|
function denyWire(reason, kind) {
|
|
11888
12125
|
const payload = kind === "PreToolUse" ? {
|
|
@@ -11894,9 +12131,16 @@ function denyWire(reason, kind) {
|
|
|
11894
12131
|
} : { decision: "block", reason };
|
|
11895
12132
|
return JSON.stringify(payload) + "\n";
|
|
11896
12133
|
}
|
|
11897
|
-
function verdict(blocks2, kind) {
|
|
12134
|
+
function verdict(blocks2, kind, audit) {
|
|
11898
12135
|
if (blocks2.length === 0) return { exitCode: 0, stdout: "" };
|
|
11899
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
|
+
}
|
|
11900
12144
|
const reason = formatDenial(blocks2);
|
|
11901
12145
|
return { exitCode: 0, stdout: denyWire(reason, kind) };
|
|
11902
12146
|
}
|
|
@@ -12062,7 +12306,7 @@ function sanctionPredictedWrites(cwd, sessionId, policy, changes) {
|
|
|
12062
12306
|
let dirty = false;
|
|
12063
12307
|
for (const c of changes) {
|
|
12064
12308
|
if (c.kind !== "file" || c.after == null || !isProtected(c.path, policy)) continue;
|
|
12065
|
-
const hash =
|
|
12309
|
+
const hash = createHash6("sha256").update(c.after).digest("hex").slice(0, 16);
|
|
12066
12310
|
const prev = tree[c.path];
|
|
12067
12311
|
tree[c.path] = {
|
|
12068
12312
|
hash,
|
|
@@ -12077,8 +12321,8 @@ function sanctionPredictedWrites(cwd, sessionId, policy, changes) {
|
|
|
12077
12321
|
if (dirty) savePtree(cwd, sessionId, tree);
|
|
12078
12322
|
}
|
|
12079
12323
|
function preToolUseVerdict(input, defaultCwd, trustedRoot) {
|
|
12324
|
+
const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
|
|
12080
12325
|
try {
|
|
12081
|
-
const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
|
|
12082
12326
|
if (trustedRoot !== void 0) {
|
|
12083
12327
|
const v = validateClaimAgainstRoot(input.cwd, trustedRoot, defaultCwd ?? process.cwd());
|
|
12084
12328
|
if (!v.ok) return failClosed("PreToolUse", `repository identity claim rejected: ${v.rejected}`);
|
|
@@ -12087,13 +12331,13 @@ function preToolUseVerdict(input, defaultCwd, trustedRoot) {
|
|
|
12087
12331
|
turnBaseline(cwd, input.session_id);
|
|
12088
12332
|
const policy = loadPolicy(cwd);
|
|
12089
12333
|
const driftBlocks = effectDriftBlocks(cwd, input.session_id, policy);
|
|
12090
|
-
if (driftBlocks) return verdict(driftBlocks, "PreToolUse");
|
|
12334
|
+
if (driftBlocks) return verdict(driftBlocks, "PreToolUse", { cwd, sessionId: input.session_id });
|
|
12091
12335
|
const changes = changesFromClaudeHook(input, cwd, sessionCwd);
|
|
12092
12336
|
const blocks2 = evaluate(changes, policy, void 0, "tool-call", { cwd }).filter((f) => f.severity === "block");
|
|
12093
12337
|
if (blocks2.length === 0) sanctionPredictedWrites(cwd, input.session_id, policy, changes);
|
|
12094
|
-
return verdict(blocks2, "PreToolUse");
|
|
12338
|
+
return verdict(blocks2, "PreToolUse", { cwd, sessionId: input.session_id });
|
|
12095
12339
|
} catch (e) {
|
|
12096
|
-
return failClosed("PreToolUse", errText2(e));
|
|
12340
|
+
return failClosed("PreToolUse", errText2(e), { cwd: sessionCwd, sessionId: input.session_id });
|
|
12097
12341
|
}
|
|
12098
12342
|
}
|
|
12099
12343
|
function cursorPath(cwd, sessionId) {
|
|
@@ -12117,19 +12361,21 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
|
|
|
12117
12361
|
const log = defaultEventLog(cwd);
|
|
12118
12362
|
const telemetry = watcherTelemetry(log);
|
|
12119
12363
|
if (telemetry.state !== "healthy") {
|
|
12120
|
-
recordObserverHealth(telemetry.state, telemetry.reason);
|
|
12364
|
+
recordObserverHealth(cwd, sessionId, telemetry.state, telemetry.reason);
|
|
12121
12365
|
}
|
|
12122
12366
|
const cp = cursorPath(cwd, sessionId);
|
|
12123
|
-
if (!
|
|
12367
|
+
if (!existsSync10(log)) return none;
|
|
12124
12368
|
let offset = 0;
|
|
12125
|
-
if (cp &&
|
|
12126
|
-
const n = Number(
|
|
12369
|
+
if (cp && existsSync10(cp)) {
|
|
12370
|
+
const n = Number(readFileSync11(cp, "utf8"));
|
|
12127
12371
|
offset = Number.isFinite(n) ? n : 0;
|
|
12128
12372
|
}
|
|
12129
12373
|
const drained = drainEvents(log, offset);
|
|
12130
12374
|
if (!drained.complete) {
|
|
12131
12375
|
const issue = drained.issue ?? "read-stalled";
|
|
12132
12376
|
recordObserverHealth(
|
|
12377
|
+
cwd,
|
|
12378
|
+
sessionId,
|
|
12133
12379
|
"degraded",
|
|
12134
12380
|
`event telemetry was not fully classified (${issue}); Stop retained the prior cursor`
|
|
12135
12381
|
);
|
|
@@ -12157,7 +12403,7 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
|
|
|
12157
12403
|
return e.kind === "file" && e.content != null ? contentHash(e.content) : null;
|
|
12158
12404
|
};
|
|
12159
12405
|
const findings = transientFindings(events, persistent, policy, finalHash);
|
|
12160
|
-
recordWarns(findings.filter((f) => f.severity !== "block"));
|
|
12406
|
+
recordWarns(cwd, sessionId, findings.filter((f) => f.severity !== "block"));
|
|
12161
12407
|
return {
|
|
12162
12408
|
blocks: findings.filter((f) => f.severity === "block"),
|
|
12163
12409
|
commit: () => {
|
|
@@ -12168,20 +12414,23 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
|
|
|
12168
12414
|
}
|
|
12169
12415
|
};
|
|
12170
12416
|
}
|
|
12171
|
-
function recordWarns(warns) {
|
|
12417
|
+
function recordWarns(cwd, sessionId, warns) {
|
|
12418
|
+
if (warns.length === 0) return;
|
|
12419
|
+
recordAuditFindings(warns, { cwd, surface: "stop", sessionId });
|
|
12172
12420
|
const log = process.env.TAMPERWARD_DENYLOG;
|
|
12173
|
-
if (!log
|
|
12421
|
+
if (!log) return;
|
|
12174
12422
|
try {
|
|
12175
|
-
|
|
12423
|
+
appendFileSync5(log, warns.map((w) => `warn:${w.rule}:${w.file ?? ""}`).join("\n") + "\n");
|
|
12176
12424
|
} catch {
|
|
12177
12425
|
}
|
|
12178
12426
|
}
|
|
12179
|
-
function recordObserverHealth(state, reason) {
|
|
12427
|
+
function recordObserverHealth(cwd, sessionId, state, reason) {
|
|
12428
|
+
recordAuditSignal("transient-observer", "warn", { cwd, surface: "stop", sessionId });
|
|
12180
12429
|
const log = process.env.TAMPERWARD_DENYLOG;
|
|
12181
12430
|
if (!log) return;
|
|
12182
12431
|
const detail = (reason ?? "").replace(/[\r\n]+/g, " ").trim();
|
|
12183
12432
|
try {
|
|
12184
|
-
|
|
12433
|
+
appendFileSync5(
|
|
12185
12434
|
log,
|
|
12186
12435
|
`warn:transient-observer:${state}${detail ? ":" + detail : ""}
|
|
12187
12436
|
`
|
|
@@ -12226,13 +12475,13 @@ function stopVerdict(input, defaultCwd, trustedRoot) {
|
|
|
12226
12475
|
saveTurnTree(cwd, input.session_id, current);
|
|
12227
12476
|
}
|
|
12228
12477
|
} catch (e) {
|
|
12229
|
-
return failClosed("Stop", errText2(e));
|
|
12478
|
+
return failClosed("Stop", errText2(e), { cwd: sessionCwd, sessionId: input.session_id });
|
|
12230
12479
|
}
|
|
12231
12480
|
if (blocks2.length === 0) {
|
|
12232
12481
|
advanceTurnBaseline(cwd, input.session_id);
|
|
12233
12482
|
commitCursor();
|
|
12234
12483
|
}
|
|
12235
|
-
return verdict(blocks2, "Stop");
|
|
12484
|
+
return verdict(blocks2, "Stop", { cwd, sessionId: input.session_id });
|
|
12236
12485
|
}
|
|
12237
12486
|
function emit(r) {
|
|
12238
12487
|
if (r.stdout) process.stdout.write(r.stdout);
|
|
@@ -12290,6 +12539,7 @@ var init_hook = __esm({
|
|
|
12290
12539
|
init_disk();
|
|
12291
12540
|
init_narrow();
|
|
12292
12541
|
init_repo_context();
|
|
12542
|
+
init_audit();
|
|
12293
12543
|
snapshotCache = null;
|
|
12294
12544
|
HookInputError = class extends Error {
|
|
12295
12545
|
};
|
|
@@ -12298,7 +12548,7 @@ var init_hook = __esm({
|
|
|
12298
12548
|
|
|
12299
12549
|
// src/ptree-cache.ts
|
|
12300
12550
|
import { lstatSync as lstatSync5 } from "node:fs";
|
|
12301
|
-
import { join as
|
|
12551
|
+
import { join as join14 } from "node:path";
|
|
12302
12552
|
function keyOf(st) {
|
|
12303
12553
|
return `${st.dev}:${st.ino}:${st.size}:${st.mode}:${st.mtimeNs}:${st.ctimeNs}`;
|
|
12304
12554
|
}
|
|
@@ -12338,7 +12588,7 @@ var init_ptree_cache = __esm({
|
|
|
12338
12588
|
/** A path the caller has reason to distrust (an fs event, a sanctioned edit):
|
|
12339
12589
|
* its entry is forgotten so the next walk hashes it whatever its stat says. */
|
|
12340
12590
|
markDirty(cwd, rel) {
|
|
12341
|
-
this.map.delete(
|
|
12591
|
+
this.map.delete(join14(cwd, rel));
|
|
12342
12592
|
}
|
|
12343
12593
|
/**
|
|
12344
12594
|
* The snapshot entry for `rel`, from the cache when the six-field stat key
|
|
@@ -12348,7 +12598,7 @@ var init_ptree_cache = __esm({
|
|
|
12348
12598
|
* time, the way the in-process path treats them.
|
|
12349
12599
|
*/
|
|
12350
12600
|
entry(cwd, rel, compute) {
|
|
12351
|
-
const abs =
|
|
12601
|
+
const abs = join14(cwd, rel);
|
|
12352
12602
|
let st;
|
|
12353
12603
|
try {
|
|
12354
12604
|
st = lstatSync5(abs, { bigint: true });
|
|
@@ -12391,13 +12641,13 @@ var init_ptree_cache = __esm({
|
|
|
12391
12641
|
});
|
|
12392
12642
|
|
|
12393
12643
|
// src/cli/hook-service.ts
|
|
12394
|
-
import { chmodSync, existsSync as
|
|
12644
|
+
import { chmodSync, existsSync as existsSync11, lstatSync as lstatSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync12, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
12395
12645
|
import { createConnection as createConnection2, createServer } from "node:net";
|
|
12396
12646
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
12397
12647
|
import { sep } from "node:path";
|
|
12398
12648
|
function readServiceState(paths) {
|
|
12399
12649
|
try {
|
|
12400
|
-
const v = JSON.parse(
|
|
12650
|
+
const v = JSON.parse(readFileSync12(paths.state, "utf8"));
|
|
12401
12651
|
if (!isRecord(v)) return null;
|
|
12402
12652
|
const { pid, version, root, started_at } = v;
|
|
12403
12653
|
if (typeof pid !== "number" || typeof version !== "string" || typeof root !== "string" || typeof started_at !== "string") return null;
|
|
@@ -12416,12 +12666,12 @@ function pidAlive2(pid) {
|
|
|
12416
12666
|
}
|
|
12417
12667
|
}
|
|
12418
12668
|
function listening(socket) {
|
|
12419
|
-
return new Promise((
|
|
12669
|
+
return new Promise((resolve17) => {
|
|
12420
12670
|
const sock = createConnection2(socket);
|
|
12421
12671
|
const done = (v) => {
|
|
12422
12672
|
clearTimeout(timer);
|
|
12423
12673
|
sock.destroy();
|
|
12424
|
-
|
|
12674
|
+
resolve17(v);
|
|
12425
12675
|
};
|
|
12426
12676
|
const timer = setTimeout(() => done(false), 1e3);
|
|
12427
12677
|
sock.once("connect", () => done(true));
|
|
@@ -12479,7 +12729,7 @@ function removeQuietly(p) {
|
|
|
12479
12729
|
}
|
|
12480
12730
|
}
|
|
12481
12731
|
function ensurePrivateDir(dir, uid) {
|
|
12482
|
-
if (!
|
|
12732
|
+
if (!existsSync11(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
12483
12733
|
const st = lstatSync6(dir);
|
|
12484
12734
|
if (!st.isDirectory()) throw new Error(`${dir} is not a directory`);
|
|
12485
12735
|
if (st.uid !== uid) throw new Error(`${dir} is owned by uid ${st.uid}, not ${uid}; refusing to serve from it`);
|
|
@@ -12574,11 +12824,11 @@ async function startHookService(opts) {
|
|
|
12574
12824
|
});
|
|
12575
12825
|
const umask = process.umask(63);
|
|
12576
12826
|
try {
|
|
12577
|
-
await new Promise((
|
|
12827
|
+
await new Promise((resolve17, reject) => {
|
|
12578
12828
|
server.once("error", reject);
|
|
12579
12829
|
server.listen(paths.socket, () => {
|
|
12580
12830
|
server.off("error", reject);
|
|
12581
|
-
|
|
12831
|
+
resolve17();
|
|
12582
12832
|
});
|
|
12583
12833
|
});
|
|
12584
12834
|
} finally {
|
|
@@ -12595,14 +12845,14 @@ async function startHookService(opts) {
|
|
|
12595
12845
|
get served() {
|
|
12596
12846
|
return served;
|
|
12597
12847
|
},
|
|
12598
|
-
close: () => new Promise((
|
|
12599
|
-
if (closed) return
|
|
12848
|
+
close: () => new Promise((resolve17) => {
|
|
12849
|
+
if (closed) return resolve17();
|
|
12600
12850
|
closed = true;
|
|
12601
12851
|
setSnapshotCache(null);
|
|
12602
12852
|
server.close(() => {
|
|
12603
12853
|
removeQuietly(paths.socket);
|
|
12604
12854
|
removeQuietly(paths.state);
|
|
12605
|
-
|
|
12855
|
+
resolve17();
|
|
12606
12856
|
});
|
|
12607
12857
|
})
|
|
12608
12858
|
};
|
|
@@ -12631,7 +12881,7 @@ async function stopHookService(paths) {
|
|
|
12631
12881
|
process.kill(state.pid, "SIGTERM");
|
|
12632
12882
|
} catch {
|
|
12633
12883
|
}
|
|
12634
|
-
const gone = () => !pidAlive2(state.pid) || !
|
|
12884
|
+
const gone = () => !pidAlive2(state.pid) || !existsSync11(paths.socket);
|
|
12635
12885
|
const until = Date.now() + STOP_WAIT_MS;
|
|
12636
12886
|
while (!gone() && Date.now() < until) sleepSync(50);
|
|
12637
12887
|
if (!gone()) throw new Error(`hook service pid ${state.pid} did not exit within ${STOP_WAIT_MS / 1e3}s`);
|
|
@@ -12816,7 +13066,7 @@ var init_verifier_limits = __esm({
|
|
|
12816
13066
|
});
|
|
12817
13067
|
|
|
12818
13068
|
// src/safe-write.ts
|
|
12819
|
-
import { randomBytes } from "node:crypto";
|
|
13069
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
12820
13070
|
import { lstatSync as lstatSync7, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
12821
13071
|
function writeTargetKind(path) {
|
|
12822
13072
|
let st;
|
|
@@ -12845,7 +13095,7 @@ function existingMode(path, fallback) {
|
|
|
12845
13095
|
return lstatSync7(path).mode & 511;
|
|
12846
13096
|
}
|
|
12847
13097
|
function atomicReplaceFile(path, content, mode) {
|
|
12848
|
-
const tmp = `${path}.${process.pid}.${
|
|
13098
|
+
const tmp = `${path}.${process.pid}.${randomBytes2(8).toString("hex")}.tmp`;
|
|
12849
13099
|
try {
|
|
12850
13100
|
writeFileSync7(tmp, content, { encoding: "utf8", flag: "wx", mode });
|
|
12851
13101
|
renameSync3(tmp, path);
|
|
@@ -12862,9 +13112,9 @@ var init_safe_write = __esm({
|
|
|
12862
13112
|
|
|
12863
13113
|
// src/cli/init.ts
|
|
12864
13114
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
12865
|
-
import { createHash as
|
|
12866
|
-
import { existsSync as
|
|
12867
|
-
import { basename as basename3, dirname as
|
|
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";
|
|
12868
13118
|
function gitConfig(cwd, key2) {
|
|
12869
13119
|
try {
|
|
12870
13120
|
return execFileSync6("git", ["config", "--get", key2], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
|
|
@@ -12912,7 +13162,7 @@ function stalePin(command) {
|
|
|
12912
13162
|
return pin !== null && (pin !== TW_VERSION || !OURS.test(command));
|
|
12913
13163
|
}
|
|
12914
13164
|
function bodyHash(body) {
|
|
12915
|
-
return
|
|
13165
|
+
return createHash7("sha256").update(body).digest("hex").slice(0, 16);
|
|
12916
13166
|
}
|
|
12917
13167
|
function workflowFile(body) {
|
|
12918
13168
|
return `${WORKFLOW_MARK} v${TW_VERSION} sha256:${bodyHash(body)}
|
|
@@ -12957,34 +13207,34 @@ function verifierCandidates(cwd) {
|
|
|
12957
13207
|
if (!candidates.includes(command)) candidates.push(command);
|
|
12958
13208
|
};
|
|
12959
13209
|
try {
|
|
12960
|
-
const pkg = JSON.parse(
|
|
13210
|
+
const pkg = JSON.parse(readFileSync13(join15(cwd, "package.json"), "utf8"));
|
|
12961
13211
|
const test = isRecord(pkg) && isRecord(pkg.scripts) ? pkg.scripts.test : void 0;
|
|
12962
13212
|
if (typeof test === "string" && test.trim() !== "" && !/error:\s*no test specified/i.test(test)) add("npm test");
|
|
12963
13213
|
} catch {
|
|
12964
13214
|
}
|
|
12965
13215
|
try {
|
|
12966
|
-
const pyproject =
|
|
13216
|
+
const pyproject = readFileSync13(join15(cwd, "pyproject.toml"), "utf8");
|
|
12967
13217
|
if (/^\s*\[tool\.pytest(?:\.ini_options)?\]\s*$/m.test(pyproject)) add("pytest");
|
|
12968
13218
|
} catch {
|
|
12969
13219
|
}
|
|
12970
|
-
if (
|
|
13220
|
+
if (existsSync12(join15(cwd, "pytest.ini"))) add("pytest");
|
|
12971
13221
|
try {
|
|
12972
|
-
const setupCfg =
|
|
13222
|
+
const setupCfg = readFileSync13(join15(cwd, "setup.cfg"), "utf8");
|
|
12973
13223
|
if (/^\s*\[tool:pytest\]\s*$/m.test(setupCfg)) add("pytest");
|
|
12974
13224
|
} catch {
|
|
12975
13225
|
}
|
|
12976
13226
|
try {
|
|
12977
|
-
const tox =
|
|
13227
|
+
const tox = readFileSync13(join15(cwd, "tox.ini"), "utf8");
|
|
12978
13228
|
if (/^\s*\[tox\]\s*$/m.test(tox)) add("tox");
|
|
12979
13229
|
} catch {
|
|
12980
13230
|
}
|
|
12981
13231
|
try {
|
|
12982
|
-
const cargo =
|
|
13232
|
+
const cargo = readFileSync13(join15(cwd, "Cargo.toml"), "utf8");
|
|
12983
13233
|
if (/^\s*\[(?:package|workspace)\]\s*(?:#.*)?$/m.test(cargo)) add("cargo test");
|
|
12984
13234
|
} catch {
|
|
12985
13235
|
}
|
|
12986
13236
|
try {
|
|
12987
|
-
const goMod =
|
|
13237
|
+
const goMod = readFileSync13(join15(cwd, "go.mod"), "utf8");
|
|
12988
13238
|
if (/^\s*module\s+\S+\s*(?:\/\/.*)?$/m.test(goMod)) add("go test ./...");
|
|
12989
13239
|
} catch {
|
|
12990
13240
|
}
|
|
@@ -13024,10 +13274,10 @@ function verifierSetupMessage(cwd) {
|
|
|
13024
13274
|
return lines.join("\n");
|
|
13025
13275
|
}
|
|
13026
13276
|
function planPolicy(cwd) {
|
|
13027
|
-
const path =
|
|
13277
|
+
const path = join15(cwd, POLICY_FILE);
|
|
13028
13278
|
const refused = refuseNonRegular(path);
|
|
13029
13279
|
if (refused) return { item: "policy", path: POLICY_FILE, status: "error", detail: refused };
|
|
13030
|
-
if (
|
|
13280
|
+
if (existsSync12(path)) {
|
|
13031
13281
|
try {
|
|
13032
13282
|
loadPolicy(cwd);
|
|
13033
13283
|
} catch (e) {
|
|
@@ -13045,8 +13295,8 @@ function planPolicy(cwd) {
|
|
|
13045
13295
|
}
|
|
13046
13296
|
function planGitignore(cwd) {
|
|
13047
13297
|
const rel = ".gitignore";
|
|
13048
|
-
const path =
|
|
13049
|
-
if (!
|
|
13298
|
+
const path = join15(cwd, rel);
|
|
13299
|
+
if (!existsSync12(join15(cwd, "node_modules"))) return null;
|
|
13050
13300
|
let existing = "";
|
|
13051
13301
|
let kind;
|
|
13052
13302
|
try {
|
|
@@ -13056,7 +13306,7 @@ function planGitignore(cwd) {
|
|
|
13056
13306
|
}
|
|
13057
13307
|
const refused = refuseNonRegular(path);
|
|
13058
13308
|
if (refused) return { item: "gitignore", path: rel, status: "error", detail: refused };
|
|
13059
|
-
if (kind === "file") existing =
|
|
13309
|
+
if (kind === "file") existing = readFileSync13(path, "utf8");
|
|
13060
13310
|
try {
|
|
13061
13311
|
const tracked = execFileSync6("git", ["ls-files", "--cached", "--", "node_modules"], {
|
|
13062
13312
|
cwd,
|
|
@@ -13107,14 +13357,14 @@ function gateVerdicts(event, groups, needle) {
|
|
|
13107
13357
|
}
|
|
13108
13358
|
function planClaudeHooks(cwd) {
|
|
13109
13359
|
const rel = ".claude/settings.json";
|
|
13110
|
-
const path =
|
|
13360
|
+
const path = join15(cwd, rel);
|
|
13111
13361
|
const refused = refuseNonRegular(path);
|
|
13112
13362
|
if (refused) return { item: "agent", path: rel, status: "error", detail: refused };
|
|
13113
13363
|
let settings = {};
|
|
13114
|
-
if (
|
|
13364
|
+
if (existsSync12(path)) {
|
|
13115
13365
|
let parsed;
|
|
13116
13366
|
try {
|
|
13117
|
-
parsed = JSON.parse(
|
|
13367
|
+
parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
13118
13368
|
} catch {
|
|
13119
13369
|
return {
|
|
13120
13370
|
item: "agent",
|
|
@@ -13163,7 +13413,7 @@ function planClaudeHooks(cwd) {
|
|
|
13163
13413
|
return {
|
|
13164
13414
|
item: "agent",
|
|
13165
13415
|
path: rel,
|
|
13166
|
-
status:
|
|
13416
|
+
status: existsSync12(path) ? "update" : "create",
|
|
13167
13417
|
detail: [
|
|
13168
13418
|
needPre && "wire PreToolUse deny",
|
|
13169
13419
|
needStop && "wire Stop sweep",
|
|
@@ -13196,7 +13446,7 @@ function planClaudeHooks(cwd) {
|
|
|
13196
13446
|
(hooks.Stop ??= []).push({ hooks: [{ type: "command", command: SWEEP_CMD }] });
|
|
13197
13447
|
}
|
|
13198
13448
|
if (needDisableFalse) settings.disableAllHooks = false;
|
|
13199
|
-
|
|
13449
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
13200
13450
|
atomicReplaceFile(path, JSON.stringify(settings, null, 2) + "\n", existingMode(path, 420));
|
|
13201
13451
|
}
|
|
13202
13452
|
};
|
|
@@ -13204,14 +13454,14 @@ function planClaudeHooks(cwd) {
|
|
|
13204
13454
|
function hooksDir(cwd) {
|
|
13205
13455
|
try {
|
|
13206
13456
|
const out3 = execFileSync6("git", ["rev-parse", "--git-path", "hooks"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
13207
|
-
if (out3) return
|
|
13457
|
+
if (out3) return resolve5(cwd, out3);
|
|
13208
13458
|
} catch {
|
|
13209
13459
|
}
|
|
13210
|
-
return
|
|
13460
|
+
return existsSync12(join15(cwd, ".git")) ? join15(cwd, ".git", "hooks") : null;
|
|
13211
13461
|
}
|
|
13212
13462
|
function display(cwd, path) {
|
|
13213
13463
|
const rel = relative3(cwd, path);
|
|
13214
|
-
return rel && !rel.startsWith("..") && !
|
|
13464
|
+
return rel && !rel.startsWith("..") && !isAbsolute9(rel) ? rel : path;
|
|
13215
13465
|
}
|
|
13216
13466
|
function unconditionalExitAt(lines) {
|
|
13217
13467
|
let depth = 0;
|
|
@@ -13234,29 +13484,29 @@ function planPreCommit(cwd) {
|
|
|
13234
13484
|
const skip = (detail) => ({ item: "pre-commit", path: "(none)", status: "skip", detail });
|
|
13235
13485
|
let path;
|
|
13236
13486
|
let note;
|
|
13237
|
-
if (
|
|
13238
|
-
path =
|
|
13487
|
+
if (existsSync12(join15(cwd, ".husky"))) {
|
|
13488
|
+
path = join15(cwd, ".husky", "pre-commit");
|
|
13239
13489
|
note = "husky";
|
|
13240
13490
|
} else {
|
|
13241
|
-
const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) =>
|
|
13491
|
+
const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync12(join15(cwd, c))));
|
|
13242
13492
|
if (managed) {
|
|
13243
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}`);
|
|
13244
13494
|
}
|
|
13245
13495
|
const dir = hooksDir(cwd);
|
|
13246
13496
|
if (!dir) return skip("not a git repo and no .husky/ \u2014 nothing to wire");
|
|
13247
|
-
if (basename3(dir) === "_" && basename3(
|
|
13248
|
-
path =
|
|
13497
|
+
if (basename3(dir) === "_" && basename3(dirname5(dir)) === ".husky") {
|
|
13498
|
+
path = join15(dirname5(dir), "pre-commit");
|
|
13249
13499
|
note = "husky";
|
|
13250
13500
|
} else {
|
|
13251
|
-
path =
|
|
13501
|
+
path = join15(dir, "pre-commit");
|
|
13252
13502
|
const rel2 = display(cwd, dir);
|
|
13253
|
-
note = rel2 ===
|
|
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)`;
|
|
13254
13504
|
}
|
|
13255
13505
|
}
|
|
13256
13506
|
const rel = display(cwd, path);
|
|
13257
13507
|
const refused = refuseNonRegular(path);
|
|
13258
13508
|
if (refused) return { item: "pre-commit", path: rel, status: "error", detail: refused };
|
|
13259
|
-
const existing =
|
|
13509
|
+
const existing = existsSync12(path) ? readFileSync13(path, "utf8") : null;
|
|
13260
13510
|
const hookMode = () => existingMode(path, 493) | 73;
|
|
13261
13511
|
if (existing === null) {
|
|
13262
13512
|
return {
|
|
@@ -13265,7 +13515,7 @@ function planPreCommit(cwd) {
|
|
|
13265
13515
|
status: "create",
|
|
13266
13516
|
detail: `create via ${note}`,
|
|
13267
13517
|
apply: () => {
|
|
13268
|
-
|
|
13518
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
13269
13519
|
atomicReplaceFile(path, `#!/bin/sh
|
|
13270
13520
|
${block.join("\n")}
|
|
13271
13521
|
`, hookMode());
|
|
@@ -13342,12 +13592,12 @@ ${block.join("\n")}
|
|
|
13342
13592
|
function planCodeowners(cwd) {
|
|
13343
13593
|
const rel = ".github/CODEOWNERS";
|
|
13344
13594
|
const existingRel = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"].find(
|
|
13345
|
-
(r) => writeTargetKind(
|
|
13595
|
+
(r) => writeTargetKind(join15(cwd, r)) !== "absent"
|
|
13346
13596
|
);
|
|
13347
|
-
const path =
|
|
13597
|
+
const path = join15(cwd, existingRel ?? rel);
|
|
13348
13598
|
const refused = refuseNonRegular(path);
|
|
13349
13599
|
if (refused) return { item: "codeowners", path: existingRel ?? rel, status: "error", detail: refused };
|
|
13350
|
-
const existing = existingRel ?
|
|
13600
|
+
const existing = existingRel ? readFileSync13(path, "utf8") : null;
|
|
13351
13601
|
const missing = CODEOWNERS_PATHS.filter((p) => existing === null || !coveredBy(existing, p));
|
|
13352
13602
|
if (existingRel !== void 0 && missing.length === 0) {
|
|
13353
13603
|
return { item: "codeowners", path: existingRel, status: "ok", detail: "gate paths already have code owners" };
|
|
@@ -13373,21 +13623,21 @@ ${CODEOWNERS_MARK}
|
|
|
13373
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.`
|
|
13374
13624
|
} : {},
|
|
13375
13625
|
apply: () => {
|
|
13376
|
-
|
|
13626
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
13377
13627
|
atomicReplaceFile(path, existing === null ? block.replace(/^\n/, "") : existing.replace(/\n?$/, "\n") + block, existingMode(path, 420));
|
|
13378
13628
|
}
|
|
13379
13629
|
};
|
|
13380
13630
|
}
|
|
13381
13631
|
function planWorkflow(cwd, force) {
|
|
13382
13632
|
const rel = ".github/workflows/tamperward.yml";
|
|
13383
|
-
const path =
|
|
13633
|
+
const path = join15(cwd, rel);
|
|
13384
13634
|
const write = () => {
|
|
13385
|
-
|
|
13635
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
13386
13636
|
atomicReplaceFile(path, workflowFile(WORKFLOW_CONTENT), existingMode(path, 420));
|
|
13387
13637
|
};
|
|
13388
13638
|
const refused = refuseNonRegular(path);
|
|
13389
13639
|
if (refused) return { item: "ci", path: rel, status: "error", detail: refused };
|
|
13390
|
-
if (!
|
|
13640
|
+
if (!existsSync12(path)) {
|
|
13391
13641
|
return {
|
|
13392
13642
|
item: "ci",
|
|
13393
13643
|
path: rel,
|
|
@@ -13398,7 +13648,7 @@ function planWorkflow(cwd, force) {
|
|
|
13398
13648
|
}
|
|
13399
13649
|
let src;
|
|
13400
13650
|
try {
|
|
13401
|
-
src =
|
|
13651
|
+
src = readFileSync13(path, "utf8");
|
|
13402
13652
|
} catch {
|
|
13403
13653
|
return { item: "ci", path: rel, status: "error", detail: "exists but cannot be read \u2014 refusing to overwrite" };
|
|
13404
13654
|
}
|
|
@@ -13459,7 +13709,7 @@ function planInit(cwd, opts = {}) {
|
|
|
13459
13709
|
];
|
|
13460
13710
|
}
|
|
13461
13711
|
function runInit(opts) {
|
|
13462
|
-
const requested =
|
|
13712
|
+
const requested = resolve5(opts.cwd ?? process.cwd());
|
|
13463
13713
|
const cwd = repoRoot(requested);
|
|
13464
13714
|
const w = opts.quiet ? { write: (_text) => true } : process.stdout;
|
|
13465
13715
|
if (cwd !== requested && !sameDir(cwd, requested)) {
|
|
@@ -13657,7 +13907,7 @@ jobs:
|
|
|
13657
13907
|
|
|
13658
13908
|
// src/fingerprint.ts
|
|
13659
13909
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
13660
|
-
import { createHash as
|
|
13910
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
13661
13911
|
function treeFingerprint(cwd, keepIgnored) {
|
|
13662
13912
|
const listed = execFileSync7("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
13663
13913
|
cwd,
|
|
@@ -13666,7 +13916,7 @@ function treeFingerprint(cwd, keepIgnored) {
|
|
|
13666
13916
|
env: trustedGitEnv()
|
|
13667
13917
|
}).split("\0").filter(Boolean);
|
|
13668
13918
|
const files = [.../* @__PURE__ */ new Set([...listed, ...keepIgnored ? ignoredPaths({ cwd }, keepIgnored) : []])].sort();
|
|
13669
|
-
const h =
|
|
13919
|
+
const h = createHash8("sha256");
|
|
13670
13920
|
for (const rel of files) {
|
|
13671
13921
|
h.update(rel);
|
|
13672
13922
|
h.update("\0");
|
|
@@ -13690,12 +13940,12 @@ var init_fingerprint = __esm({
|
|
|
13690
13940
|
});
|
|
13691
13941
|
|
|
13692
13942
|
// src/dependency-env.ts
|
|
13693
|
-
import { createHash as
|
|
13943
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
13694
13944
|
import {
|
|
13695
|
-
existsSync as
|
|
13945
|
+
existsSync as existsSync13,
|
|
13696
13946
|
lstatSync as lstatSync8,
|
|
13697
13947
|
readdirSync as readdirSync3,
|
|
13698
|
-
readFileSync as
|
|
13948
|
+
readFileSync as readFileSync14,
|
|
13699
13949
|
readlinkSync as readlinkSync2,
|
|
13700
13950
|
realpathSync as realpathSync6,
|
|
13701
13951
|
statSync as statSync3
|
|
@@ -13703,19 +13953,19 @@ import {
|
|
|
13703
13953
|
import {
|
|
13704
13954
|
basename as basename4,
|
|
13705
13955
|
delimiter,
|
|
13706
|
-
dirname as
|
|
13707
|
-
isAbsolute as
|
|
13708
|
-
join as
|
|
13956
|
+
dirname as dirname6,
|
|
13957
|
+
isAbsolute as isAbsolute10,
|
|
13958
|
+
join as join16,
|
|
13709
13959
|
relative as relative4,
|
|
13710
|
-
resolve as
|
|
13960
|
+
resolve as resolve6,
|
|
13711
13961
|
sep as sep2
|
|
13712
13962
|
} from "node:path";
|
|
13713
13963
|
function metricsFor(descriptor) {
|
|
13714
13964
|
return descriptor.metrics ??= { fullSnapshots: 0, reusedSnapshots: 0, totalMs: 0 };
|
|
13715
13965
|
}
|
|
13716
13966
|
function inside(root, candidate) {
|
|
13717
|
-
const rel = relative4(
|
|
13718
|
-
return rel === "" || rel !== ".." && !rel.startsWith(".." + sep2) && !
|
|
13967
|
+
const rel = relative4(resolve6(root), resolve6(candidate));
|
|
13968
|
+
return rel === "" || rel !== ".." && !rel.startsWith(".." + sep2) && !isAbsolute10(rel);
|
|
13719
13969
|
}
|
|
13720
13970
|
function modeKind(path) {
|
|
13721
13971
|
const st = lstatSync8(path);
|
|
@@ -13753,7 +14003,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13753
14003
|
st = statSync3(path);
|
|
13754
14004
|
if (!st.isFile()) throw new Error("not a regular file");
|
|
13755
14005
|
h.update(label + "\0external-file\0" + path + "\0" + String(st.mode) + "\0");
|
|
13756
|
-
h.update(
|
|
14006
|
+
h.update(readFileSync14(path));
|
|
13757
14007
|
h.update("\0");
|
|
13758
14008
|
} catch (e) {
|
|
13759
14009
|
throw new Unattestable(
|
|
@@ -13774,7 +14024,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13774
14024
|
);
|
|
13775
14025
|
}
|
|
13776
14026
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
13777
|
-
const path =
|
|
14027
|
+
const path = join16(dir, entry.name);
|
|
13778
14028
|
const rel = namespace ? namespace + "/" + entry.name : entry.name;
|
|
13779
14029
|
let ident;
|
|
13780
14030
|
try {
|
|
@@ -13787,7 +14037,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13787
14037
|
h.update(rel + "\0" + ident.kind + "\0" + String(ident.mode) + "\0");
|
|
13788
14038
|
if (ident.kind === "file") {
|
|
13789
14039
|
try {
|
|
13790
|
-
h.update(
|
|
14040
|
+
h.update(readFileSync14(path));
|
|
13791
14041
|
h.update("\0");
|
|
13792
14042
|
} catch (e) {
|
|
13793
14043
|
throw new Unattestable(
|
|
@@ -13843,10 +14093,10 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13843
14093
|
walk2(root.realPath, "");
|
|
13844
14094
|
}
|
|
13845
14095
|
function snapshot(roots, probes, cwd) {
|
|
13846
|
-
const h =
|
|
14096
|
+
const h = createHash9("sha256");
|
|
13847
14097
|
for (const probe of probes) {
|
|
13848
14098
|
h.update(
|
|
13849
|
-
"probe\0" + probe.kind + "\0" + probe.path + "\0" + (
|
|
14099
|
+
"probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync13(probe.path) ? "present" : "absent") + "\0"
|
|
13850
14100
|
);
|
|
13851
14101
|
}
|
|
13852
14102
|
for (const root of roots) fingerprintRoot(root, cwd, h);
|
|
@@ -13867,7 +14117,7 @@ function freezeRoot(kind, path) {
|
|
|
13867
14117
|
if (!statSync3(path).isDirectory()) {
|
|
13868
14118
|
throw new Error("not a directory");
|
|
13869
14119
|
}
|
|
13870
|
-
return { kind, path:
|
|
14120
|
+
return { kind, path: resolve6(path), realPath: realpathSync6(path) };
|
|
13871
14121
|
} catch (e) {
|
|
13872
14122
|
throw new Unattestable(
|
|
13873
14123
|
kind + " root " + JSON.stringify(path) + " is not resolvable (" + (e instanceof Error ? e.message : String(e)) + ")"
|
|
@@ -13903,17 +14153,17 @@ function firstExecutable(command) {
|
|
|
13903
14153
|
function resolveExecutable(token, cwd, env) {
|
|
13904
14154
|
if (!token) return null;
|
|
13905
14155
|
if (token.includes("/") || token.includes("\\")) {
|
|
13906
|
-
const path =
|
|
14156
|
+
const path = isAbsolute10(token) ? token : resolve6(cwd, token);
|
|
13907
14157
|
try {
|
|
13908
|
-
return
|
|
14158
|
+
return existsSync13(path) ? realpathSync6(path) : null;
|
|
13909
14159
|
} catch {
|
|
13910
14160
|
return null;
|
|
13911
14161
|
}
|
|
13912
14162
|
}
|
|
13913
14163
|
for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
13914
|
-
const candidate =
|
|
14164
|
+
const candidate = resolve6(entry, token);
|
|
13915
14165
|
try {
|
|
13916
|
-
if (
|
|
14166
|
+
if (existsSync13(candidate) && statSync3(candidate).isFile()) return realpathSync6(candidate);
|
|
13917
14167
|
} catch {
|
|
13918
14168
|
}
|
|
13919
14169
|
}
|
|
@@ -13930,13 +14180,13 @@ function looksPython(command, executable, token) {
|
|
|
13930
14180
|
}
|
|
13931
14181
|
function inferredVenvFromToken(token, cwd) {
|
|
13932
14182
|
if (!token || !token.includes("/") && !token.includes("\\")) return null;
|
|
13933
|
-
const path =
|
|
14183
|
+
const path = isAbsolute10(token) ? token : resolve6(cwd, token);
|
|
13934
14184
|
if (!pythonName(basename4(path))) return null;
|
|
13935
|
-
const parent =
|
|
14185
|
+
const parent = dirname6(path);
|
|
13936
14186
|
const parentName = basename4(parent).toLowerCase();
|
|
13937
14187
|
if (parentName !== "bin" && parentName !== "scripts") return null;
|
|
13938
|
-
const root =
|
|
13939
|
-
return
|
|
14188
|
+
const root = dirname6(parent);
|
|
14189
|
+
return existsSync13(join16(root, "pyvenv.cfg")) ? root : null;
|
|
13940
14190
|
}
|
|
13941
14191
|
function unsupportedEcosystem(command, token) {
|
|
13942
14192
|
const name = token ? basename4(token).toLowerCase() : "";
|
|
@@ -13948,17 +14198,17 @@ function unsupportedEcosystem(command, token) {
|
|
|
13948
14198
|
}
|
|
13949
14199
|
function inferredVenv(executable) {
|
|
13950
14200
|
if (!executable) return null;
|
|
13951
|
-
const parent =
|
|
14201
|
+
const parent = dirname6(executable);
|
|
13952
14202
|
const parentName = basename4(parent).toLowerCase();
|
|
13953
14203
|
if (parentName !== "bin" && parentName !== "scripts") return null;
|
|
13954
|
-
const root =
|
|
13955
|
-
return
|
|
14204
|
+
const root = dirname6(parent);
|
|
14205
|
+
return existsSync13(join16(root, "pyvenv.cfg")) ? root : null;
|
|
13956
14206
|
}
|
|
13957
14207
|
function venvProblem(root) {
|
|
13958
|
-
const cfg =
|
|
13959
|
-
if (!
|
|
14208
|
+
const cfg = join16(root, "pyvenv.cfg");
|
|
14209
|
+
if (!existsSync13(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
|
|
13960
14210
|
try {
|
|
13961
|
-
const body =
|
|
14211
|
+
const body = readFileSync14(cfg, "utf8");
|
|
13962
14212
|
if (/^\s*include-system-site-packages\s*=\s*true\s*$/im.test(body)) {
|
|
13963
14213
|
return "Python environment " + JSON.stringify(root) + " enables system site-packages; those external package roots are not bounded";
|
|
13964
14214
|
}
|
|
@@ -13968,7 +14218,7 @@ function venvProblem(root) {
|
|
|
13968
14218
|
return null;
|
|
13969
14219
|
}
|
|
13970
14220
|
function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
|
|
13971
|
-
const cwd =
|
|
14221
|
+
const cwd = resolve6(cwdInput);
|
|
13972
14222
|
const roots = [];
|
|
13973
14223
|
const probes = [];
|
|
13974
14224
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -13982,21 +14232,21 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
|
|
|
13982
14232
|
}
|
|
13983
14233
|
};
|
|
13984
14234
|
try {
|
|
13985
|
-
const nodeModules =
|
|
13986
|
-
if (
|
|
14235
|
+
const nodeModules = join16(cwd, "node_modules");
|
|
14236
|
+
if (existsSync13(nodeModules)) addRoot("node_modules", nodeModules);
|
|
13987
14237
|
else probes.push({ kind: "node_modules", path: nodeModules });
|
|
13988
14238
|
const token = firstExecutable(command);
|
|
13989
14239
|
const executable = resolveExecutable(token, cwd, env);
|
|
13990
14240
|
let venv = null;
|
|
13991
14241
|
if (env.VIRTUAL_ENV?.trim()) {
|
|
13992
|
-
if (!
|
|
14242
|
+
if (!isAbsolute10(env.VIRTUAL_ENV)) {
|
|
13993
14243
|
throw new Unattestable("VIRTUAL_ENV is relative (" + JSON.stringify(env.VIRTUAL_ENV) + ")");
|
|
13994
14244
|
}
|
|
13995
|
-
venv =
|
|
14245
|
+
venv = resolve6(env.VIRTUAL_ENV);
|
|
13996
14246
|
} else {
|
|
13997
14247
|
venv = inferredVenv(executable) ?? inferredVenvFromToken(token, cwd);
|
|
13998
|
-
if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) &&
|
|
13999
|
-
venv =
|
|
14248
|
+
if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync13(join16(cwd, ".venv", "pyvenv.cfg"))) {
|
|
14249
|
+
venv = join16(cwd, ".venv");
|
|
14000
14250
|
}
|
|
14001
14251
|
}
|
|
14002
14252
|
if (venv) {
|
|
@@ -14032,7 +14282,7 @@ function checkDependencyEnvironment(cwdInput, descriptor) {
|
|
|
14032
14282
|
const fingerprint2 = measuredSnapshot(
|
|
14033
14283
|
descriptor.roots,
|
|
14034
14284
|
descriptor.probes,
|
|
14035
|
-
|
|
14285
|
+
resolve6(cwdInput),
|
|
14036
14286
|
metricsFor(descriptor)
|
|
14037
14287
|
);
|
|
14038
14288
|
if (fingerprint2 !== descriptor.fingerprint) {
|
|
@@ -14045,7 +14295,7 @@ function checkDependencyEnvironment(cwdInput, descriptor) {
|
|
|
14045
14295
|
}
|
|
14046
14296
|
function attestDependencyEnvironment(cwdInput, descriptor) {
|
|
14047
14297
|
const attestation = {
|
|
14048
|
-
cwd:
|
|
14298
|
+
cwd: resolve6(cwdInput),
|
|
14049
14299
|
descriptor,
|
|
14050
14300
|
check: checkDependencyEnvironment(cwdInput, descriptor)
|
|
14051
14301
|
};
|
|
@@ -14053,7 +14303,7 @@ function attestDependencyEnvironment(cwdInput, descriptor) {
|
|
|
14053
14303
|
return attestation;
|
|
14054
14304
|
}
|
|
14055
14305
|
function reuseDependencyEnvironmentAttestation(cwdInput, descriptor, attestation) {
|
|
14056
|
-
if (!attestation || !issuedAttestations.has(attestation) || attestation.descriptor !== descriptor || attestation.cwd !==
|
|
14306
|
+
if (!attestation || !issuedAttestations.has(attestation) || attestation.descriptor !== descriptor || attestation.cwd !== resolve6(cwdInput)) return null;
|
|
14057
14307
|
metricsFor(descriptor).reusedSnapshots += 1;
|
|
14058
14308
|
return attestation.check;
|
|
14059
14309
|
}
|
|
@@ -14102,7 +14352,7 @@ var init_dependency_env = __esm({
|
|
|
14102
14352
|
import { spawnSync } from "node:child_process";
|
|
14103
14353
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
14104
14354
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
14105
|
-
import { join as
|
|
14355
|
+
import { join as join17 } from "node:path";
|
|
14106
14356
|
function emptySuiteDiagnostics() {
|
|
14107
14357
|
return {
|
|
14108
14358
|
stdout: { ...EMPTY_STREAM },
|
|
@@ -14181,8 +14431,8 @@ function parseCapturedSupervisorResult(stdoutText) {
|
|
|
14181
14431
|
};
|
|
14182
14432
|
}
|
|
14183
14433
|
function runCapturedProcessSync(executable, args, opts) {
|
|
14184
|
-
const stateDir = mkdtempSync2(
|
|
14185
|
-
const configFile =
|
|
14434
|
+
const stateDir = mkdtempSync2(join17(tmpdir3(), "tw-suite-capture-"));
|
|
14435
|
+
const configFile = join17(stateDir, "config.json");
|
|
14186
14436
|
const backstop = opts.backstopMs ?? 3e4;
|
|
14187
14437
|
try {
|
|
14188
14438
|
writeFileSync8(
|
|
@@ -14417,10 +14667,10 @@ if (child) {
|
|
|
14417
14667
|
});
|
|
14418
14668
|
|
|
14419
14669
|
// src/verifier-backend.ts
|
|
14420
|
-
import { createHash as
|
|
14670
|
+
import { createHash as createHash10, randomUUID } from "node:crypto";
|
|
14421
14671
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
14422
|
-
import { accessSync as accessSync2, constants as constants3, readFileSync as
|
|
14423
|
-
import { delimiter as delimiter2, dirname as
|
|
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";
|
|
14424
14674
|
function identity() {
|
|
14425
14675
|
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
14426
14676
|
const groups = typeof process.getgroups === "function" ? process.getgroups() : [];
|
|
@@ -14440,12 +14690,12 @@ function writableByCaller(path) {
|
|
|
14440
14690
|
}
|
|
14441
14691
|
}
|
|
14442
14692
|
function replaceableByCaller(path) {
|
|
14443
|
-
let current =
|
|
14693
|
+
let current = resolve7(path);
|
|
14444
14694
|
if (writableByCaller(current)) return true;
|
|
14445
|
-
current =
|
|
14695
|
+
current = dirname7(current);
|
|
14446
14696
|
while (true) {
|
|
14447
14697
|
if (writableByCaller(current)) return true;
|
|
14448
|
-
const parent =
|
|
14698
|
+
const parent = dirname7(current);
|
|
14449
14699
|
if (parent === current) break;
|
|
14450
14700
|
current = parent;
|
|
14451
14701
|
}
|
|
@@ -14454,7 +14704,7 @@ function replaceableByCaller(path) {
|
|
|
14454
14704
|
function findExecutable(name) {
|
|
14455
14705
|
for (const raw of (process.env.PATH ?? "").split(delimiter2)) {
|
|
14456
14706
|
if (!raw) continue;
|
|
14457
|
-
const candidate =
|
|
14707
|
+
const candidate = join18(raw, name);
|
|
14458
14708
|
try {
|
|
14459
14709
|
accessSync2(candidate, constants3.X_OK);
|
|
14460
14710
|
return realpathSync7(candidate);
|
|
@@ -14464,7 +14714,7 @@ function findExecutable(name) {
|
|
|
14464
14714
|
return null;
|
|
14465
14715
|
}
|
|
14466
14716
|
function fileSha256(path) {
|
|
14467
|
-
return
|
|
14717
|
+
return createHash10("sha256").update(readFileSync16(path)).digest("hex");
|
|
14468
14718
|
}
|
|
14469
14719
|
function engineClientEnv() {
|
|
14470
14720
|
return {
|
|
@@ -14786,7 +15036,7 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
14786
15036
|
const args = containerRunArgs({
|
|
14787
15037
|
image: backend.image,
|
|
14788
15038
|
name,
|
|
14789
|
-
workspace:
|
|
15039
|
+
workspace: resolve7(dir),
|
|
14790
15040
|
command,
|
|
14791
15041
|
uid,
|
|
14792
15042
|
gid,
|
|
@@ -14797,7 +15047,7 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
14797
15047
|
backend.enginePath,
|
|
14798
15048
|
dockerArgs(backend.daemonHost, args),
|
|
14799
15049
|
{
|
|
14800
|
-
cwd:
|
|
15050
|
+
cwd: dirname7(backend.enginePath),
|
|
14801
15051
|
env: engineClientEnv(),
|
|
14802
15052
|
timeoutMs: budgetSecs * 1e3,
|
|
14803
15053
|
backstopMs: 3e4
|
|
@@ -14897,10 +15147,10 @@ var init_verifier_backend = __esm({
|
|
|
14897
15147
|
|
|
14898
15148
|
// src/cli/verify.ts
|
|
14899
15149
|
import { execFileSync as execFileSync8, spawnSync as spawnSync3 } from "node:child_process";
|
|
14900
|
-
import { createHash as
|
|
14901
|
-
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync9, mkdirSync as
|
|
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";
|
|
14902
15152
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
14903
|
-
import { dirname as
|
|
15153
|
+
import { dirname as dirname8, isAbsolute as isAbsolute11, join as join19, relative as relative5, resolve as resolve8, sep as sep3 } from "node:path";
|
|
14904
15154
|
function oracleAssuranceReport() {
|
|
14905
15155
|
return {
|
|
14906
15156
|
level: "suite-exit-only",
|
|
@@ -14926,8 +15176,8 @@ function baseIsAncestorOfHead(base, cwd) {
|
|
|
14926
15176
|
return r.status === 0;
|
|
14927
15177
|
}
|
|
14928
15178
|
function inside2(root, path) {
|
|
14929
|
-
const rel = relative5(
|
|
14930
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !
|
|
15179
|
+
const rel = relative5(resolve8(root), resolve8(path));
|
|
15180
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute11(rel);
|
|
14931
15181
|
}
|
|
14932
15182
|
function linkParts(path) {
|
|
14933
15183
|
const parts = sep3 === "\\" ? path.split(/[\\/]+/) : path.split("/");
|
|
@@ -14940,10 +15190,10 @@ function driveRelativeLinkTarget(target) {
|
|
|
14940
15190
|
return sep3 === "\\" && /^[A-Za-z]:(?![\\/])/.test(target);
|
|
14941
15191
|
}
|
|
14942
15192
|
function rootedLinkTarget(target) {
|
|
14943
|
-
return
|
|
15193
|
+
return isAbsolute11(target) || driveRelativeLinkTarget(target);
|
|
14944
15194
|
}
|
|
14945
15195
|
function safeSymlink(target, out3, root, label) {
|
|
14946
|
-
if (rootedLinkTarget(target) || !inside2(root,
|
|
15196
|
+
if (rootedLinkTarget(target) || !inside2(root, resolve8(dirname8(out3), target))) {
|
|
14947
15197
|
throw linkEscape(label, target);
|
|
14948
15198
|
}
|
|
14949
15199
|
rmSync7(out3, { force: true });
|
|
@@ -14951,7 +15201,7 @@ function safeSymlink(target, out3, root, label) {
|
|
|
14951
15201
|
}
|
|
14952
15202
|
function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
14953
15203
|
let domain = "tree";
|
|
14954
|
-
let stack = linkParts(relative5(root,
|
|
15204
|
+
let stack = linkParts(relative5(root, dirname8(out3)));
|
|
14955
15205
|
const pending = linkParts(target);
|
|
14956
15206
|
const seen = /* @__PURE__ */ new Set();
|
|
14957
15207
|
let hops = 0;
|
|
@@ -14968,7 +15218,7 @@ function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
|
14968
15218
|
continue;
|
|
14969
15219
|
}
|
|
14970
15220
|
const base = domain === "deps" && dependencyRoot ? dependencyRoot : root;
|
|
14971
|
-
const current =
|
|
15221
|
+
const current = join19(base, ...stack);
|
|
14972
15222
|
let st;
|
|
14973
15223
|
try {
|
|
14974
15224
|
st = lstatSync9(current);
|
|
@@ -14989,12 +15239,12 @@ function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
|
14989
15239
|
if (driveRelativeLinkTarget(next)) {
|
|
14990
15240
|
throw linkEscape(label, target);
|
|
14991
15241
|
}
|
|
14992
|
-
if (
|
|
15242
|
+
if (isAbsolute11(next)) {
|
|
14993
15243
|
if (domain === "tree" || !dependencyRoot || !inside2(dependencyRoot, next)) {
|
|
14994
15244
|
throw linkEscape(label, target);
|
|
14995
15245
|
}
|
|
14996
15246
|
stack = [];
|
|
14997
|
-
pending.unshift(...linkParts(relative5(dependencyRoot,
|
|
15247
|
+
pending.unshift(...linkParts(relative5(dependencyRoot, resolve8(next))));
|
|
14998
15248
|
} else {
|
|
14999
15249
|
pending.unshift(...linkParts(next));
|
|
15000
15250
|
}
|
|
@@ -15004,7 +15254,7 @@ function rejectLinkedParent(cwd, rel) {
|
|
|
15004
15254
|
const parts = rel.split("/").slice(0, -1);
|
|
15005
15255
|
let current = cwd;
|
|
15006
15256
|
for (const part of parts) {
|
|
15007
|
-
current =
|
|
15257
|
+
current = join19(current, part);
|
|
15008
15258
|
let st;
|
|
15009
15259
|
try {
|
|
15010
15260
|
st = lstatSync9(current);
|
|
@@ -15020,7 +15270,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
15020
15270
|
const listed = workingTreePaths(cwd);
|
|
15021
15271
|
const links = [];
|
|
15022
15272
|
for (const rel of listed) {
|
|
15023
|
-
const src =
|
|
15273
|
+
const src = join19(cwd, rel);
|
|
15024
15274
|
rejectLinkedParent(cwd, rel);
|
|
15025
15275
|
let st;
|
|
15026
15276
|
try {
|
|
@@ -15028,8 +15278,8 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
15028
15278
|
} catch {
|
|
15029
15279
|
continue;
|
|
15030
15280
|
}
|
|
15031
|
-
const out3 =
|
|
15032
|
-
|
|
15281
|
+
const out3 = join19(dest, rel);
|
|
15282
|
+
mkdirSync8(dirname8(out3), { recursive: true });
|
|
15033
15283
|
dropSymlink(out3);
|
|
15034
15284
|
if (st.isSymbolicLink()) {
|
|
15035
15285
|
const target = readlinkSync3(src);
|
|
@@ -15045,17 +15295,17 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
15045
15295
|
for (const link of links) {
|
|
15046
15296
|
validateSymlinkGraph(link.target, link.out, dest, dependencyRoot, link.rel);
|
|
15047
15297
|
}
|
|
15048
|
-
if (dependencyRoot) symlinkSync(dependencyRoot,
|
|
15298
|
+
if (dependencyRoot) symlinkSync(dependencyRoot, join19(dest, "node_modules"), "dir");
|
|
15049
15299
|
}
|
|
15050
15300
|
function workingTreePaths(cwd) {
|
|
15051
15301
|
return git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
15052
15302
|
}
|
|
15053
15303
|
function probeFilesystemCaseSensitivity(dir) {
|
|
15054
|
-
const upper =
|
|
15055
|
-
const lower =
|
|
15304
|
+
const upper = join19(dir, ".tw-case-probe-A");
|
|
15305
|
+
const lower = join19(dir, ".tw-case-probe-a");
|
|
15056
15306
|
try {
|
|
15057
15307
|
writeFileSync9(upper, "", { flag: "wx" });
|
|
15058
|
-
return !
|
|
15308
|
+
return !existsSync14(lower);
|
|
15059
15309
|
} finally {
|
|
15060
15310
|
rmSync7(upper, { force: true });
|
|
15061
15311
|
}
|
|
@@ -15111,15 +15361,15 @@ function baseEntries(base, cwd) {
|
|
|
15111
15361
|
});
|
|
15112
15362
|
}
|
|
15113
15363
|
function overlayDigest(dest, paths) {
|
|
15114
|
-
const h =
|
|
15364
|
+
const h = createHash11("sha256");
|
|
15115
15365
|
for (const rel of [...paths].sort()) {
|
|
15116
15366
|
h.update(rel);
|
|
15117
15367
|
h.update("\0");
|
|
15118
|
-
const p =
|
|
15368
|
+
const p = join19(dest, rel);
|
|
15119
15369
|
try {
|
|
15120
15370
|
const st = lstatSync9(p);
|
|
15121
15371
|
h.update(String(st.mode));
|
|
15122
|
-
h.update(st.isSymbolicLink() ? readlinkSync3(p) :
|
|
15372
|
+
h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync17(p));
|
|
15123
15373
|
} catch {
|
|
15124
15374
|
h.update("<gone>");
|
|
15125
15375
|
}
|
|
@@ -15149,8 +15399,8 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
15149
15399
|
throw new Error(`the base's ${e.path} is a submodule; the pristine copy cannot reproduce it`);
|
|
15150
15400
|
}
|
|
15151
15401
|
const content = execFileSync8("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
|
|
15152
|
-
const out3 =
|
|
15153
|
-
|
|
15402
|
+
const out3 = join19(dest, e.path);
|
|
15403
|
+
mkdirSync8(dirname8(out3), { recursive: true });
|
|
15154
15404
|
rmSync7(out3, { force: true });
|
|
15155
15405
|
if (e.mode === "120000") {
|
|
15156
15406
|
const target = content.toString("utf8");
|
|
@@ -15166,7 +15416,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
15166
15416
|
const inCopy = workingTreePaths(cwd);
|
|
15167
15417
|
for (const rel of inCopy) {
|
|
15168
15418
|
if (!isOverlay(rel) || baseProtected.has(fold(rel))) continue;
|
|
15169
|
-
rmSync7(
|
|
15419
|
+
rmSync7(join19(dest, rel), { force: true });
|
|
15170
15420
|
removed++;
|
|
15171
15421
|
}
|
|
15172
15422
|
for (const link of restoredLinks) {
|
|
@@ -15180,8 +15430,8 @@ function suiteEnv(scratch) {
|
|
|
15180
15430
|
if (DROPPED_ENV.has(k) || PINNED_NPM.test(k)) continue;
|
|
15181
15431
|
env[k] = v;
|
|
15182
15432
|
}
|
|
15183
|
-
const userRc =
|
|
15184
|
-
const globalRc =
|
|
15433
|
+
const userRc = join19(scratch, "npmrc-user");
|
|
15434
|
+
const globalRc = join19(scratch, "npmrc-global");
|
|
15185
15435
|
for (const f of [userRc, globalRc]) writeFileSync9(f, "", { mode: 292 });
|
|
15186
15436
|
env.npm_config_userconfig = userRc;
|
|
15187
15437
|
env.npm_config_globalconfig = globalRc;
|
|
@@ -15213,7 +15463,7 @@ function runLocalSuite(dir, cmd, budgetSecs) {
|
|
|
15213
15463
|
reason: `checkpointed-local verifier is unsupported on ${process.platform}; no trusted local shell contract is defined`
|
|
15214
15464
|
};
|
|
15215
15465
|
}
|
|
15216
|
-
const scratch = mkdtempSync3(
|
|
15466
|
+
const scratch = mkdtempSync3(join19(tmpdir4(), "tw-verify-run-"));
|
|
15217
15467
|
try {
|
|
15218
15468
|
const r = runCapturedProcessSync(shell.executable, shell.args, {
|
|
15219
15469
|
cwd: dir,
|
|
@@ -15402,8 +15652,8 @@ function runVerify(opts) {
|
|
|
15402
15652
|
} catch {
|
|
15403
15653
|
return cannotVerify("BASE_UNRESOLVABLE", `cannot resolve base rev "${opts.base ?? "HEAD"}"`);
|
|
15404
15654
|
}
|
|
15405
|
-
const visRoot = mkdtempSync3(
|
|
15406
|
-
const visDir =
|
|
15655
|
+
const visRoot = mkdtempSync3(join19(tmpdir4(), "tw-verify-vis-"));
|
|
15656
|
+
const visDir = join19(visRoot, "t");
|
|
15407
15657
|
const cleanup = (dirs) => {
|
|
15408
15658
|
if (opts.keep) return;
|
|
15409
15659
|
for (const d of dirs) rmSync7(d, { recursive: true, force: true });
|
|
@@ -15412,7 +15662,7 @@ function runVerify(opts) {
|
|
|
15412
15662
|
if (!filesystemCaseSensitive) {
|
|
15413
15663
|
const present = (rel) => {
|
|
15414
15664
|
try {
|
|
15415
|
-
lstatSync9(
|
|
15665
|
+
lstatSync9(join19(cwd, rel));
|
|
15416
15666
|
return true;
|
|
15417
15667
|
} catch {
|
|
15418
15668
|
return false;
|
|
@@ -15444,7 +15694,7 @@ function runVerify(opts) {
|
|
|
15444
15694
|
);
|
|
15445
15695
|
}
|
|
15446
15696
|
try {
|
|
15447
|
-
|
|
15697
|
+
mkdirSync8(visDir);
|
|
15448
15698
|
materialize(cwd, visDir, frozenNodeModules);
|
|
15449
15699
|
} catch (e) {
|
|
15450
15700
|
cleanup([visRoot]);
|
|
@@ -15498,12 +15748,12 @@ function runVerify(opts) {
|
|
|
15498
15748
|
{ stage: "visible", dependency_environment: dependencyReport(), verifier_backend: backendReport(), oracle_assurance: oracleAssuranceReport() }
|
|
15499
15749
|
);
|
|
15500
15750
|
}
|
|
15501
|
-
const priRoot = mkdtempSync3(
|
|
15502
|
-
const priDir =
|
|
15751
|
+
const priRoot = mkdtempSync3(join19(tmpdir4(), "tw-verify-pri-"));
|
|
15752
|
+
const priDir = join19(priRoot, "t");
|
|
15503
15753
|
let restored = [];
|
|
15504
15754
|
let removedAdded = 0;
|
|
15505
15755
|
try {
|
|
15506
|
-
|
|
15756
|
+
mkdirSync8(priDir);
|
|
15507
15757
|
materialize(cwd, priDir, frozenNodeModules);
|
|
15508
15758
|
({ restored, removed: removedAdded } = overlayPristine(
|
|
15509
15759
|
cwd,
|
|
@@ -15772,10 +16022,10 @@ var init_verify = __esm({
|
|
|
15772
16022
|
|
|
15773
16023
|
// src/cli/run.ts
|
|
15774
16024
|
import { execFileSync as execFileSync9, spawn, spawnSync as spawnSync4 } from "node:child_process";
|
|
15775
|
-
import { randomBytes as
|
|
15776
|
-
import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as
|
|
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";
|
|
15777
16027
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
15778
|
-
import { dirname as
|
|
16028
|
+
import { dirname as dirname9, join as join20, resolve as resolve9 } from "node:path";
|
|
15779
16029
|
function authoritativeRunLifecyclePlatform(platform = process.platform) {
|
|
15780
16030
|
return platform === "linux";
|
|
15781
16031
|
}
|
|
@@ -15812,7 +16062,7 @@ function writableByCaller2(path) {
|
|
|
15812
16062
|
return true;
|
|
15813
16063
|
} catch {
|
|
15814
16064
|
}
|
|
15815
|
-
const parent =
|
|
16065
|
+
const parent = dirname9(cur);
|
|
15816
16066
|
if (parent === cur) break;
|
|
15817
16067
|
cur = parent;
|
|
15818
16068
|
}
|
|
@@ -15851,9 +16101,9 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
|
|
|
15851
16101
|
};
|
|
15852
16102
|
}
|
|
15853
16103
|
function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecycleTestMode, machineMode = false) {
|
|
15854
|
-
const stateDir = mkdtempSync4(
|
|
15855
|
-
const resultFile =
|
|
15856
|
-
const agentEnvFile =
|
|
16104
|
+
const stateDir = mkdtempSync4(join20(tmpdir5(), "tw-agent-supervisor-"));
|
|
16105
|
+
const resultFile = join20(stateDir, "result.json");
|
|
16106
|
+
const agentEnvFile = join20(stateDir, "agent-env.json");
|
|
15857
16107
|
try {
|
|
15858
16108
|
const linux = process.platform === "linux";
|
|
15859
16109
|
let executable;
|
|
@@ -15927,7 +16177,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
|
|
|
15927
16177
|
}
|
|
15928
16178
|
let state = {};
|
|
15929
16179
|
try {
|
|
15930
|
-
state = JSON.parse(
|
|
16180
|
+
state = JSON.parse(readFileSync18(resultFile, "utf8"));
|
|
15931
16181
|
} catch {
|
|
15932
16182
|
return {
|
|
15933
16183
|
exit: 1,
|
|
@@ -15971,7 +16221,7 @@ function observerExited(pid) {
|
|
|
15971
16221
|
if (!pidAlive3(pid)) return true;
|
|
15972
16222
|
if (process.platform !== "linux") return false;
|
|
15973
16223
|
try {
|
|
15974
|
-
const stat =
|
|
16224
|
+
const stat = readFileSync18(`/proc/${pid}/stat`, "utf8");
|
|
15975
16225
|
const state = stat.slice(stat.lastIndexOf(")") + 2).split(" ")[0];
|
|
15976
16226
|
return state === "Z" || state === "X";
|
|
15977
16227
|
} catch (e) {
|
|
@@ -15979,11 +16229,11 @@ function observerExited(pid) {
|
|
|
15979
16229
|
}
|
|
15980
16230
|
}
|
|
15981
16231
|
function supervisedObserverLog(cwd) {
|
|
15982
|
-
const gd = gitDir(cwd) ??
|
|
15983
|
-
return
|
|
16232
|
+
const gd = gitDir(cwd) ?? join20(cwd, ".git");
|
|
16233
|
+
return join20(
|
|
15984
16234
|
gd,
|
|
15985
16235
|
"tamperward",
|
|
15986
|
-
`run-observer-${process.pid}-${Date.now()}-${
|
|
16236
|
+
`run-observer-${process.pid}-${Date.now()}-${randomBytes3(4).toString("hex")}.jsonl`
|
|
15987
16237
|
);
|
|
15988
16238
|
}
|
|
15989
16239
|
function startSupervisedObserver(cwd, base, entry) {
|
|
@@ -15994,7 +16244,7 @@ function startSupervisedObserver(cwd, base, entry) {
|
|
|
15994
16244
|
try {
|
|
15995
16245
|
child = spawn(
|
|
15996
16246
|
process.execPath,
|
|
15997
|
-
[
|
|
16247
|
+
[resolve9(cliEntry), "watch", "--dir", cwd, "--log", log, "--base", base],
|
|
15998
16248
|
{
|
|
15999
16249
|
cwd,
|
|
16000
16250
|
stdio: ["ignore", "ignore", "inherit"],
|
|
@@ -16093,7 +16343,7 @@ function git3(args, cwd) {
|
|
|
16093
16343
|
}
|
|
16094
16344
|
function nowTicks() {
|
|
16095
16345
|
try {
|
|
16096
|
-
return Math.floor(parseFloat(
|
|
16346
|
+
return Math.floor(parseFloat(readFileSync18("/proc/uptime", "utf8").split(" ")[0]) * 100);
|
|
16097
16347
|
} catch {
|
|
16098
16348
|
return Number.POSITIVE_INFINITY;
|
|
16099
16349
|
}
|
|
@@ -16116,7 +16366,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
16116
16366
|
const n = Number(pid);
|
|
16117
16367
|
if (n === process.pid) continue;
|
|
16118
16368
|
try {
|
|
16119
|
-
const stat =
|
|
16369
|
+
const stat = readFileSync18(`/proc/${pid}/stat`, "utf8");
|
|
16120
16370
|
const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
|
|
16121
16371
|
if (!Number.isFinite(startTicks) || startTicks < spawnedAfterTicks) continue;
|
|
16122
16372
|
} catch {
|
|
@@ -16131,7 +16381,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
16131
16381
|
for (const link of procLinks) {
|
|
16132
16382
|
try {
|
|
16133
16383
|
const raw = readlinkSync4(link).replace(/ \(deleted\)$/, "");
|
|
16134
|
-
const held = raw.startsWith("/") ?
|
|
16384
|
+
const held = raw.startsWith("/") ? resolve9(raw) : raw;
|
|
16135
16385
|
if (held === real || held.startsWith(real + "/")) {
|
|
16136
16386
|
holds = true;
|
|
16137
16387
|
break;
|
|
@@ -16144,7 +16394,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
16144
16394
|
return out3;
|
|
16145
16395
|
}
|
|
16146
16396
|
function runEnvelope(opts) {
|
|
16147
|
-
const cwd =
|
|
16397
|
+
const cwd = resolve9(opts.cwd ?? process.cwd());
|
|
16148
16398
|
const say = opts.json ? (_s) => {
|
|
16149
16399
|
} : out;
|
|
16150
16400
|
if (!opts.argv.length) {
|
|
@@ -16688,8 +16938,8 @@ raise SystemExit(0)
|
|
|
16688
16938
|
|
|
16689
16939
|
// src/cli/doctor.ts
|
|
16690
16940
|
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
16691
|
-
import { existsSync as
|
|
16692
|
-
import { join as
|
|
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";
|
|
16693
16943
|
function lifecyclePlatformCheck(platform = process.platform, linuxPython = platform === "linux" ? trustedLinuxPython() : null) {
|
|
16694
16944
|
if (platform === "linux") {
|
|
16695
16945
|
if (linuxPython?.path) {
|
|
@@ -16744,10 +16994,10 @@ function wiringState(status, authority) {
|
|
|
16744
16994
|
return authority ? "BROKEN" : "WARN";
|
|
16745
16995
|
}
|
|
16746
16996
|
function pinsInFile(path) {
|
|
16747
|
-
if (!
|
|
16997
|
+
if (!existsSync15(path)) return [];
|
|
16748
16998
|
let src = "";
|
|
16749
16999
|
try {
|
|
16750
|
-
src =
|
|
17000
|
+
src = readFileSync19(path, "utf8");
|
|
16751
17001
|
} catch {
|
|
16752
17002
|
return [];
|
|
16753
17003
|
}
|
|
@@ -16767,14 +17017,14 @@ function workflowPermissionCheck(cwd, workflowRels = [".github/workflows/tamperw
|
|
|
16767
17017
|
const broken = [];
|
|
16768
17018
|
const warnings = [];
|
|
16769
17019
|
for (const rel of workflowRels) {
|
|
16770
|
-
const path =
|
|
16771
|
-
if (!
|
|
17020
|
+
const path = resolve10(cwd, rel);
|
|
17021
|
+
if (!existsSync15(path)) {
|
|
16772
17022
|
broken.push(`${rel} is missing`);
|
|
16773
17023
|
continue;
|
|
16774
17024
|
}
|
|
16775
17025
|
let doc;
|
|
16776
17026
|
try {
|
|
16777
|
-
doc = yaml.parse(
|
|
17027
|
+
doc = yaml.parse(readFileSync19(path, "utf8"));
|
|
16778
17028
|
} catch (e) {
|
|
16779
17029
|
broken.push(`${rel} is not valid YAML (${e instanceof Error ? e.message : String(e)})`);
|
|
16780
17030
|
continue;
|
|
@@ -16887,9 +17137,9 @@ function collectLocalPosture(cwd, policy, authorityWorkflows, explicitWorkflow =
|
|
|
16887
17137
|
addWiring("codeowners", "codeowners", true);
|
|
16888
17138
|
checks.push(workflowPermissionCheck(cwd, authorityWorkflows));
|
|
16889
17139
|
const pins = [
|
|
16890
|
-
...pinsInFile(
|
|
16891
|
-
...pinsInFile(
|
|
16892
|
-
...pinsInFile(
|
|
17140
|
+
...pinsInFile(join21(cwd, ".claude", "settings.json")),
|
|
17141
|
+
...pinsInFile(join21(cwd, ".git", "hooks", "pre-commit")),
|
|
17142
|
+
...pinsInFile(join21(cwd, ".husky", "pre-commit"))
|
|
16893
17143
|
];
|
|
16894
17144
|
const below = pins.filter((pin) => (compareVersions(pin, TW_VERSION) ?? 0) < 0);
|
|
16895
17145
|
const above = pins.filter((pin) => (compareVersions(TW_VERSION, pin) ?? 0) < 0);
|
|
@@ -17114,7 +17364,7 @@ function verifyJobs(doc) {
|
|
|
17114
17364
|
return found;
|
|
17115
17365
|
}
|
|
17116
17366
|
function diagnose(opts = {}) {
|
|
17117
|
-
const cwd = repoRoot(
|
|
17367
|
+
const cwd = repoRoot(resolve10(opts.cwd ?? process.cwd()));
|
|
17118
17368
|
let policy;
|
|
17119
17369
|
try {
|
|
17120
17370
|
policy = policyFor(opts, cwd);
|
|
@@ -17138,8 +17388,8 @@ function diagnose(opts = {}) {
|
|
|
17138
17388
|
workflowRels.push(opts.workflow);
|
|
17139
17389
|
} else {
|
|
17140
17390
|
const workflowDirRel = ".github/workflows";
|
|
17141
|
-
const workflowDir =
|
|
17142
|
-
if (!
|
|
17391
|
+
const workflowDir = resolve10(cwd, workflowDirRel);
|
|
17392
|
+
if (!existsSync15(workflowDir)) {
|
|
17143
17393
|
return fail("ci-verifier", `${workflowDirRel}: workflow directory does not exist`);
|
|
17144
17394
|
}
|
|
17145
17395
|
let entries;
|
|
@@ -17151,20 +17401,20 @@ function diagnose(opts = {}) {
|
|
|
17151
17401
|
`${workflowDirRel}: could not enumerate workflows (${e instanceof Error ? e.message : String(e)})`
|
|
17152
17402
|
);
|
|
17153
17403
|
}
|
|
17154
|
-
for (const name of entries) workflowRels.push(
|
|
17404
|
+
for (const name of entries) workflowRels.push(join21(workflowDirRel, name));
|
|
17155
17405
|
}
|
|
17156
17406
|
const requiredSecs = requiredVerifierAuthoritySeconds(policy.verify.budget);
|
|
17157
17407
|
const requiredMinutes = Math.ceil(requiredSecs / 60);
|
|
17158
17408
|
let verifyJobCount = 0;
|
|
17159
17409
|
const authorityWorkflows = [];
|
|
17160
17410
|
for (const workflowRel of workflowRels) {
|
|
17161
|
-
const workflowPath =
|
|
17162
|
-
if (!
|
|
17411
|
+
const workflowPath = resolve10(cwd, workflowRel);
|
|
17412
|
+
if (!existsSync15(workflowPath)) {
|
|
17163
17413
|
return fail("ci-verifier", `${workflowRel}: workflow does not exist`);
|
|
17164
17414
|
}
|
|
17165
17415
|
let doc;
|
|
17166
17416
|
try {
|
|
17167
|
-
doc = yaml.parse(
|
|
17417
|
+
doc = yaml.parse(readFileSync19(workflowPath, "utf8"));
|
|
17168
17418
|
} catch (e) {
|
|
17169
17419
|
return fail(
|
|
17170
17420
|
"ci-verifier",
|
|
@@ -17320,16 +17570,16 @@ var init_doctor = __esm({
|
|
|
17320
17570
|
// src/cli/trace-verify.ts
|
|
17321
17571
|
import { execFileSync as execFileSync11, spawnSync as spawnSync5 } from "node:child_process";
|
|
17322
17572
|
import {
|
|
17323
|
-
existsSync as
|
|
17573
|
+
existsSync as existsSync16,
|
|
17324
17574
|
mkdtempSync as mkdtempSync5,
|
|
17325
|
-
readFileSync as
|
|
17575
|
+
readFileSync as readFileSync20,
|
|
17326
17576
|
readdirSync as readdirSync6,
|
|
17327
17577
|
realpathSync as realpathSync10,
|
|
17328
17578
|
rmSync as rmSync9,
|
|
17329
17579
|
symlinkSync as symlinkSync2
|
|
17330
17580
|
} from "node:fs";
|
|
17331
17581
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
17332
|
-
import { isAbsolute as
|
|
17582
|
+
import { isAbsolute as isAbsolute12, join as join22, relative as relative6, resolve as resolve11, sep as sep4 } from "node:path";
|
|
17333
17583
|
function unescapeStraceString(s) {
|
|
17334
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, "\\");
|
|
17335
17585
|
}
|
|
@@ -17354,11 +17604,11 @@ function parseStraceFileAccess(raw) {
|
|
|
17354
17604
|
return out3;
|
|
17355
17605
|
}
|
|
17356
17606
|
function inside3(root, path) {
|
|
17357
|
-
const rel = relative6(
|
|
17358
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !
|
|
17607
|
+
const rel = relative6(resolve11(root), resolve11(path));
|
|
17608
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !isAbsolute12(rel);
|
|
17359
17609
|
}
|
|
17360
17610
|
function normalRepoPath(root, raw) {
|
|
17361
|
-
const abs =
|
|
17611
|
+
const abs = isAbsolute12(raw) ? resolve11(raw) : resolve11(root, raw);
|
|
17362
17612
|
if (!inside3(root, abs)) return null;
|
|
17363
17613
|
const rel = relative6(root, abs).split(sep4).join("/");
|
|
17364
17614
|
return rel && rel !== ".." ? rel : null;
|
|
@@ -17378,7 +17628,7 @@ function summarizeTraceRuns(opts) {
|
|
|
17378
17628
|
for (const item of run) {
|
|
17379
17629
|
const rel = normalRepoPath(opts.root, item.path);
|
|
17380
17630
|
const isTracked = rel !== null && opts.tracked.has(rel);
|
|
17381
|
-
const keyPath = rel !== null ? opts.tracked.has(rel) ? rel : null :
|
|
17631
|
+
const keyPath = rel !== null ? opts.tracked.has(rel) ? rel : null : resolve11(opts.root, item.path);
|
|
17382
17632
|
if (keyPath === null) continue;
|
|
17383
17633
|
const target = isTracked ? repo : external;
|
|
17384
17634
|
const key2 = `${isTracked ? "repo" : "external"}\0${keyPath}`;
|
|
@@ -17455,36 +17705,36 @@ function materializeBase(base, cwd, dest) {
|
|
|
17455
17705
|
`tar extraction failed${untar.stderr?.length ? `: ${String(untar.stderr).trim()}` : ""}`
|
|
17456
17706
|
);
|
|
17457
17707
|
}
|
|
17458
|
-
const hostModules =
|
|
17459
|
-
const tracedModules =
|
|
17460
|
-
if (
|
|
17461
|
-
symlinkSync2(
|
|
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");
|
|
17462
17712
|
}
|
|
17463
17713
|
}
|
|
17464
17714
|
function traceFiles(prefix) {
|
|
17465
|
-
const dir =
|
|
17715
|
+
const dir = resolve11(prefix, "..");
|
|
17466
17716
|
const base = prefix.split(sep4).at(-1) ?? prefix;
|
|
17467
|
-
return readdirSync6(dir).filter((name) => name === base || name.startsWith(base + ".")).map((name) =>
|
|
17717
|
+
return readdirSync6(dir).filter((name) => name === base || name.startsWith(base + ".")).map((name) => join22(dir, name)).sort();
|
|
17468
17718
|
}
|
|
17469
17719
|
function rewriteTraceRoot(accesses, actualRoot) {
|
|
17470
17720
|
return accesses.map((item) => {
|
|
17471
|
-
const abs =
|
|
17721
|
+
const abs = isAbsolute12(item.path) ? resolve11(item.path) : resolve11(actualRoot, item.path);
|
|
17472
17722
|
if (!inside3(actualRoot, abs)) return { ...item, path: abs };
|
|
17473
17723
|
const rel = relative6(actualRoot, abs).split(sep4).join("/");
|
|
17474
17724
|
try {
|
|
17475
|
-
const real =
|
|
17725
|
+
const real = resolve11(realpathSync10(abs));
|
|
17476
17726
|
if (!inside3(actualRoot, real)) return { ...item, path: real };
|
|
17477
17727
|
} catch {
|
|
17478
17728
|
}
|
|
17479
|
-
return { ...item, path:
|
|
17729
|
+
return { ...item, path: join22(TRACE_ROOT, ...rel.split("/")) };
|
|
17480
17730
|
});
|
|
17481
17731
|
}
|
|
17482
17732
|
function runOneTrace(cwd, base, command, budget) {
|
|
17483
|
-
const root = mkdtempSync5(
|
|
17484
|
-
const traceDir = mkdtempSync5(
|
|
17733
|
+
const root = mkdtempSync5(join22(tmpdir6(), "tw-trace-base-"));
|
|
17734
|
+
const traceDir = mkdtempSync5(join22(tmpdir6(), "tw-trace-log-"));
|
|
17485
17735
|
try {
|
|
17486
17736
|
materializeBase(base, cwd, root);
|
|
17487
|
-
const prefix =
|
|
17737
|
+
const prefix = join22(traceDir, "trace");
|
|
17488
17738
|
const traced = spawnSync5(
|
|
17489
17739
|
"strace",
|
|
17490
17740
|
[
|
|
@@ -17517,7 +17767,7 @@ function runOneTrace(cwd, base, command, budget) {
|
|
|
17517
17767
|
if (logs.length === 0) {
|
|
17518
17768
|
throw new Error("strace produced no trace files");
|
|
17519
17769
|
}
|
|
17520
|
-
const accesses = logs.flatMap((path) => parseStraceFileAccess(
|
|
17770
|
+
const accesses = logs.flatMap((path) => parseStraceFileAccess(readFileSync20(path, "utf8")));
|
|
17521
17771
|
return {
|
|
17522
17772
|
exit: traced.status ?? 1,
|
|
17523
17773
|
accesses: rewriteTraceRoot(accesses, root)
|
|
@@ -17561,7 +17811,7 @@ function renderText2(report2) {
|
|
|
17561
17811
|
for (const note of report2.notes) out3(`note: ${note}`);
|
|
17562
17812
|
}
|
|
17563
17813
|
function runTraceVerify(opts = {}) {
|
|
17564
|
-
const cwd =
|
|
17814
|
+
const cwd = resolve11(opts.cwd ?? process.cwd());
|
|
17565
17815
|
if (process.platform !== "linux") {
|
|
17566
17816
|
process.stderr.write("tamperward trace-verify: Linux only in this release; unsupported platform \u2014 no parity is implied.\n");
|
|
17567
17817
|
return 2;
|
|
@@ -17706,11 +17956,88 @@ var init_trace_verify = __esm({
|
|
|
17706
17956
|
}
|
|
17707
17957
|
});
|
|
17708
17958
|
|
|
17959
|
+
// src/runtimes.ts
|
|
17960
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
17961
|
+
import { join as join23 } from "node:path";
|
|
17962
|
+
function detectRuntimes(cwd) {
|
|
17963
|
+
const found = [];
|
|
17964
|
+
for (const rt of KNOWN_RUNTIMES) {
|
|
17965
|
+
const matchedMarker = rt.markers.find((m) => {
|
|
17966
|
+
try {
|
|
17967
|
+
return existsSync17(join23(cwd, m));
|
|
17968
|
+
} catch {
|
|
17969
|
+
return false;
|
|
17970
|
+
}
|
|
17971
|
+
});
|
|
17972
|
+
if (matchedMarker !== void 0) found.push({ ...rt, matchedMarker });
|
|
17973
|
+
}
|
|
17974
|
+
return found;
|
|
17975
|
+
}
|
|
17976
|
+
function hasInLoopRuntime(detected) {
|
|
17977
|
+
return detected.some((r) => r.steering === "in-loop");
|
|
17978
|
+
}
|
|
17979
|
+
function detectionHeadline(detected) {
|
|
17980
|
+
if (detected.length === 0) {
|
|
17981
|
+
return "No agent runtime detected. Wiring Claude Code in-loop steering plus the agent-neutral layers (pre-commit + CI) that protect any agent or human.";
|
|
17982
|
+
}
|
|
17983
|
+
if (detected.length === 1) {
|
|
17984
|
+
const r = detected[0];
|
|
17985
|
+
return `Detected ${r.label} \u2014 ${r.note}`;
|
|
17986
|
+
}
|
|
17987
|
+
const parts = detected.map((r) => `${r.label} (${r.steering === "in-loop" ? "in-loop" : "neutral layers"})`);
|
|
17988
|
+
return `Detected ${detected.length} agent runtimes: ${parts.join(", ")}.`;
|
|
17989
|
+
}
|
|
17990
|
+
function neutralOnlyCaveat(detected) {
|
|
17991
|
+
if (detected.length === 0 || hasInLoopRuntime(detected)) return null;
|
|
17992
|
+
const names = detected.map((r) => r.label).join(", ");
|
|
17993
|
+
const tracking = [...new Set(detected.map((r) => r.adapterTracking).filter(Boolean))].join(", ");
|
|
17994
|
+
return `In-loop steering (deny-before-execute) ships for Claude Code only today, so on ${names} your live protection is the agent-neutral layers: pre-commit and CI. The Claude hook wiring is still written and is inert unless Claude Code runs in this repository.${tracking ? ` A native in-loop adapter is tracked in ${tracking}.` : ""}`;
|
|
17995
|
+
}
|
|
17996
|
+
var KNOWN_RUNTIMES;
|
|
17997
|
+
var init_runtimes = __esm({
|
|
17998
|
+
"src/runtimes.ts"() {
|
|
17999
|
+
"use strict";
|
|
18000
|
+
KNOWN_RUNTIMES = [
|
|
18001
|
+
{
|
|
18002
|
+
id: "claude-code",
|
|
18003
|
+
label: "Claude Code",
|
|
18004
|
+
markers: [".claude/settings.json", ".claude"],
|
|
18005
|
+
steering: "in-loop",
|
|
18006
|
+
note: "native in-loop steering (PreToolUse deny + Stop sweep) plus the agent-neutral layers."
|
|
18007
|
+
},
|
|
18008
|
+
{
|
|
18009
|
+
id: "cursor",
|
|
18010
|
+
label: "Cursor",
|
|
18011
|
+
markers: [".cursor", ".cursorrules"],
|
|
18012
|
+
steering: "neutral",
|
|
18013
|
+
adapterTracking: "#482",
|
|
18014
|
+
note: "agent-neutral layers (pre-commit + CI) today; a native in-loop adapter is tracked in #482."
|
|
18015
|
+
},
|
|
18016
|
+
{
|
|
18017
|
+
id: "copilot",
|
|
18018
|
+
label: "GitHub Copilot",
|
|
18019
|
+
markers: [".github/copilot-instructions.md", ".github/copilot"],
|
|
18020
|
+
steering: "neutral",
|
|
18021
|
+
adapterTracking: "#482",
|
|
18022
|
+
note: "agent-neutral layers (pre-commit + CI) today; a native in-loop adapter is tracked in #482."
|
|
18023
|
+
},
|
|
18024
|
+
{
|
|
18025
|
+
id: "codex",
|
|
18026
|
+
label: "an AGENTS.md-aware agent (e.g. Codex)",
|
|
18027
|
+
markers: [".codex", "AGENTS.md"],
|
|
18028
|
+
steering: "neutral",
|
|
18029
|
+
adapterTracking: "#482",
|
|
18030
|
+
note: "agent-neutral layers (pre-commit + CI) today; a native in-loop adapter is tracked in #482."
|
|
18031
|
+
}
|
|
18032
|
+
];
|
|
18033
|
+
}
|
|
18034
|
+
});
|
|
18035
|
+
|
|
17709
18036
|
// src/cli/onboard.ts
|
|
17710
18037
|
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
17711
|
-
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as
|
|
18038
|
+
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync21, realpathSync as realpathSync11, rmSync as rmSync10 } from "node:fs";
|
|
17712
18039
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
17713
|
-
import { join as
|
|
18040
|
+
import { join as join24, relative as relative7, resolve as resolve12, sep as sep5 } from "node:path";
|
|
17714
18041
|
import { createInterface } from "node:readline";
|
|
17715
18042
|
import { parseDocument } from "yaml";
|
|
17716
18043
|
function readlineAsker(input, output) {
|
|
@@ -17719,9 +18046,9 @@ function readlineAsker(input, output) {
|
|
|
17719
18046
|
let pending = null;
|
|
17720
18047
|
rl.on("close", () => {
|
|
17721
18048
|
closed = true;
|
|
17722
|
-
const
|
|
18049
|
+
const resolve17 = pending;
|
|
17723
18050
|
pending = null;
|
|
17724
|
-
|
|
18051
|
+
resolve17?.(null);
|
|
17725
18052
|
});
|
|
17726
18053
|
rl.on("SIGINT", () => rl.close());
|
|
17727
18054
|
const ask = (question) => new Promise((resolvePromise) => {
|
|
@@ -17747,7 +18074,7 @@ function terminalText(text) {
|
|
|
17747
18074
|
return stripControl(text).replace(/\s+/g, " ").trim();
|
|
17748
18075
|
}
|
|
17749
18076
|
function committableSetupPath(cwd, path) {
|
|
17750
|
-
const rel = relative7(cwd,
|
|
18077
|
+
const rel = relative7(cwd, resolve12(cwd, path));
|
|
17751
18078
|
if (!rel || rel === ".." || rel.startsWith(".." + sep5)) return false;
|
|
17752
18079
|
return rel !== ".git" && !rel.startsWith(".git" + sep5);
|
|
17753
18080
|
}
|
|
@@ -17778,13 +18105,13 @@ function explainVerdict(v, code2) {
|
|
|
17778
18105
|
}
|
|
17779
18106
|
}
|
|
17780
18107
|
function writeVerifyCommand(cwd, command) {
|
|
17781
|
-
const path =
|
|
18108
|
+
const path = join24(cwd, POLICY_FILE);
|
|
17782
18109
|
const kind = writeTargetKind(path);
|
|
17783
18110
|
if (kind === "symlink" || kind === "irregular") {
|
|
17784
18111
|
return `${POLICY_FILE} is not a regular file; refusing to follow or replace a symlink or special file`;
|
|
17785
18112
|
}
|
|
17786
18113
|
const mode = existingMode(path, 420);
|
|
17787
|
-
const original = kind === "file" ?
|
|
18114
|
+
const original = kind === "file" ? readFileSync21(path, "utf8") : null;
|
|
17788
18115
|
let next;
|
|
17789
18116
|
try {
|
|
17790
18117
|
const doc = parseDocument(original && original.trim() ? original : "version: 1\n");
|
|
@@ -17807,7 +18134,7 @@ function writeVerifyCommand(cwd, command) {
|
|
|
17807
18134
|
}
|
|
17808
18135
|
}
|
|
17809
18136
|
async function runOnboard(opts, io = {}) {
|
|
17810
|
-
const requestedCwd =
|
|
18137
|
+
const requestedCwd = resolve12(opts.cwd ?? process.cwd());
|
|
17811
18138
|
const out3 = io.out ?? ((line) => void process.stdout.write(line + "\n"));
|
|
17812
18139
|
const errLine = (line) => void process.stderr.write(line + "\n");
|
|
17813
18140
|
const rawErr = (line) => errLine(terminalText(line));
|
|
@@ -17847,7 +18174,7 @@ async function runOnboard(opts, io = {}) {
|
|
|
17847
18174
|
fail(requestedCwd + " is not inside a Git repository. Run `git init` first.");
|
|
17848
18175
|
return 2;
|
|
17849
18176
|
}
|
|
17850
|
-
const cwd =
|
|
18177
|
+
const cwd = resolve12(rootText);
|
|
17851
18178
|
let sameRoot = cwd === requestedCwd;
|
|
17852
18179
|
try {
|
|
17853
18180
|
sameRoot = realpathSync11(cwd) === realpathSync11(requestedCwd);
|
|
@@ -17932,11 +18259,15 @@ async function runOnboard(opts, io = {}) {
|
|
|
17932
18259
|
}
|
|
17933
18260
|
}
|
|
17934
18261
|
section(2);
|
|
18262
|
+
const detected = detectRuntimes(cwd);
|
|
18263
|
+
status("RUNTIME", detectionHeadline(detected), "info");
|
|
18264
|
+
const caveat = neutralOnlyCaveat(detected);
|
|
18265
|
+
if (caveat) status("NOTE", caveat, "warn");
|
|
17935
18266
|
const plan = planInit(cwd);
|
|
17936
18267
|
const names = {
|
|
17937
18268
|
policy: "Policy",
|
|
17938
18269
|
gitignore: "Git ignore",
|
|
17939
|
-
agent: "Claude
|
|
18270
|
+
agent: "In-loop (Claude)",
|
|
17940
18271
|
"pre-commit": "Pre-commit",
|
|
17941
18272
|
ci: "CI workflow",
|
|
17942
18273
|
codeowners: "CODEOWNERS"
|
|
@@ -18173,13 +18504,13 @@ function runDemo(cwd, head, runners, out3) {
|
|
|
18173
18504
|
}
|
|
18174
18505
|
const before = treeFingerprint(cwd);
|
|
18175
18506
|
out3(`working tree fingerprint before: ${before.slice(0, 16)}`);
|
|
18176
|
-
const wt = mkdtempSync6(
|
|
18507
|
+
const wt = mkdtempSync6(join24(tmpdir7(), "tw-onboard-demo-"));
|
|
18177
18508
|
try {
|
|
18178
18509
|
if (git5(cwd, ["worktree", "add", "--detach", wt, head]) === null) {
|
|
18179
18510
|
out3("Demo skipped: could not create a temporary worktree (git worktree add failed).");
|
|
18180
18511
|
return;
|
|
18181
18512
|
}
|
|
18182
|
-
const targetPath =
|
|
18513
|
+
const targetPath = join24(wt, target.path);
|
|
18183
18514
|
const targetStat = lstatSync10(targetPath, { throwIfNoEntry: false });
|
|
18184
18515
|
if (!targetStat || !targetStat.isFile() || targetStat.isSymbolicLink()) {
|
|
18185
18516
|
out3("Demo skipped: the selected test is not a regular file in the disposable worktree.");
|
|
@@ -18234,6 +18565,7 @@ var init_onboard = __esm({
|
|
|
18234
18565
|
init_policy();
|
|
18235
18566
|
init_policy_load();
|
|
18236
18567
|
init_narrow();
|
|
18568
|
+
init_runtimes();
|
|
18237
18569
|
init_wiring();
|
|
18238
18570
|
init_safe_write();
|
|
18239
18571
|
Aborted = class extends Error {
|
|
@@ -18264,7 +18596,7 @@ var init_onboard = __esm({
|
|
|
18264
18596
|
});
|
|
18265
18597
|
|
|
18266
18598
|
// src/research/adapter.ts
|
|
18267
|
-
import { isAbsolute as
|
|
18599
|
+
import { isAbsolute as isAbsolute13, resolve as resolve13 } from "node:path";
|
|
18268
18600
|
function taskEnv(task) {
|
|
18269
18601
|
return {
|
|
18270
18602
|
TAMPERWARD_RESEARCH_TASK: task.id,
|
|
@@ -18280,7 +18612,7 @@ function normalizeCommandArgv(argv, cwd = process.cwd()) {
|
|
|
18280
18612
|
throw new ResearchError('the command adapter needs an agent command after "--"');
|
|
18281
18613
|
}
|
|
18282
18614
|
const head = argv[0];
|
|
18283
|
-
return head.includes("/") && !
|
|
18615
|
+
return head.includes("/") && !isAbsolute13(head) ? [resolve13(cwd, head), ...argv.slice(1)] : [...argv];
|
|
18284
18616
|
}
|
|
18285
18617
|
function commandAdapter(argv, cwd = process.cwd()) {
|
|
18286
18618
|
const anchored = normalizeCommandArgv(argv, cwd);
|
|
@@ -18379,9 +18711,9 @@ var init_capture = __esm({
|
|
|
18379
18711
|
});
|
|
18380
18712
|
|
|
18381
18713
|
// src/research/manifest.ts
|
|
18382
|
-
import { createHash as
|
|
18383
|
-
import { readFileSync as
|
|
18384
|
-
import { dirname as
|
|
18714
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
18715
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
18716
|
+
import { dirname as dirname10, resolve as resolve14 } from "node:path";
|
|
18385
18717
|
function taskFrom(raw, index, manifestDir) {
|
|
18386
18718
|
const where = `task #${index + 1}`;
|
|
18387
18719
|
if (!isRecord(raw)) throw new ResearchError(`task manifest: ${where} is not an object`);
|
|
@@ -18404,17 +18736,17 @@ function taskFrom(raw, index, manifestDir) {
|
|
|
18404
18736
|
const isUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(repo) || /^[^/]+@[^:]+:/.test(repo);
|
|
18405
18737
|
return {
|
|
18406
18738
|
id,
|
|
18407
|
-
repo: isUrl ? repo :
|
|
18739
|
+
repo: isUrl ? repo : resolve14(manifestDir, repo),
|
|
18408
18740
|
base: base ?? "HEAD",
|
|
18409
18741
|
prompt,
|
|
18410
18742
|
verify: { command: verify.command, ...budget !== void 0 ? { budget } : {} }
|
|
18411
18743
|
};
|
|
18412
18744
|
}
|
|
18413
18745
|
function readManifest(path) {
|
|
18414
|
-
const abs =
|
|
18746
|
+
const abs = resolve14(path);
|
|
18415
18747
|
let bytes;
|
|
18416
18748
|
try {
|
|
18417
|
-
bytes =
|
|
18749
|
+
bytes = readFileSync22(abs);
|
|
18418
18750
|
} catch (e) {
|
|
18419
18751
|
throw new ResearchError(`cannot read task manifest ${abs}: ${e instanceof Error ? e.message : String(e)}`);
|
|
18420
18752
|
}
|
|
@@ -18431,13 +18763,13 @@ function readManifest(path) {
|
|
|
18431
18763
|
if (!Array.isArray(raw.tasks) || raw.tasks.length === 0) {
|
|
18432
18764
|
throw new ResearchError(`task manifest ${abs}: no tasks (tasks must be a non-empty array)`);
|
|
18433
18765
|
}
|
|
18434
|
-
const tasks = raw.tasks.map((t, i) => taskFrom(t, i,
|
|
18766
|
+
const tasks = raw.tasks.map((t, i) => taskFrom(t, i, dirname10(abs)));
|
|
18435
18767
|
const seen = /* @__PURE__ */ new Set();
|
|
18436
18768
|
for (const t of tasks) {
|
|
18437
18769
|
if (seen.has(t.id)) throw new ResearchError(`task manifest ${abs}: duplicate task id "${t.id}"`);
|
|
18438
18770
|
seen.add(t.id);
|
|
18439
18771
|
}
|
|
18440
|
-
return { path: abs, sha256:
|
|
18772
|
+
return { path: abs, sha256: createHash13("sha256").update(bytes).digest("hex"), tasks };
|
|
18441
18773
|
}
|
|
18442
18774
|
var MANIFEST_VERSION;
|
|
18443
18775
|
var init_manifest = __esm({
|
|
@@ -18626,18 +18958,18 @@ var init_record = __esm({
|
|
|
18626
18958
|
|
|
18627
18959
|
// src/research/run.ts
|
|
18628
18960
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
18629
|
-
import { existsSync as
|
|
18630
|
-
import { join as
|
|
18961
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync23, renameSync as renameSync4, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
|
|
18962
|
+
import { join as join25, resolve as resolve15 } from "node:path";
|
|
18631
18963
|
function git6(args, cwd) {
|
|
18632
18964
|
return execFileSync13("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
18633
18965
|
}
|
|
18634
18966
|
function pairRecordPath(ledger, task, pair) {
|
|
18635
|
-
return
|
|
18967
|
+
return join25(ledger, "pairs", `${task}--${pair}.json`);
|
|
18636
18968
|
}
|
|
18637
18969
|
function resumableRecord(path, expected) {
|
|
18638
18970
|
let raw;
|
|
18639
18971
|
try {
|
|
18640
|
-
raw = JSON.parse(
|
|
18972
|
+
raw = JSON.parse(readFileSync23(path, "utf8"));
|
|
18641
18973
|
} catch (e) {
|
|
18642
18974
|
throw new ResearchError(`ledger record ${path} is malformed (${errorMessage(e).split("\n")[0]}); remove it to regenerate the pair, or use a new --out`);
|
|
18643
18975
|
}
|
|
@@ -18678,9 +19010,9 @@ function writeRecordAtomically(path, text) {
|
|
|
18678
19010
|
renameSync4(tmp, path);
|
|
18679
19011
|
}
|
|
18680
19012
|
function freshWorkspace(ledger, task, pair, arm, sourceBase) {
|
|
18681
|
-
const ws3 =
|
|
19013
|
+
const ws3 = join25(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
|
|
18682
19014
|
rmSync11(ws3, { recursive: true, force: true });
|
|
18683
|
-
|
|
19015
|
+
mkdirSync9(join25(ledger, "workspaces"), { recursive: true });
|
|
18684
19016
|
try {
|
|
18685
19017
|
execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo, ws3], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
18686
19018
|
} catch (e) {
|
|
@@ -18926,16 +19258,16 @@ function runResearch(opts) {
|
|
|
18926
19258
|
err2("tamperward research: the gated arm cannot start here (see `tamperward doctor`); run as a non-root user on Linux.");
|
|
18927
19259
|
return 2;
|
|
18928
19260
|
}
|
|
18929
|
-
const ledger =
|
|
19261
|
+
const ledger = resolve15(opts.out);
|
|
18930
19262
|
const pairs = opts.pairs ?? 1;
|
|
18931
|
-
|
|
19263
|
+
mkdirSync9(join25(ledger, "pairs"), { recursive: true });
|
|
18932
19264
|
for (const task of tasks) {
|
|
18933
19265
|
const existingRecords = /* @__PURE__ */ new Map();
|
|
18934
19266
|
let sourceBase = null;
|
|
18935
19267
|
try {
|
|
18936
19268
|
for (let pair = 1; pair <= pairs; pair++) {
|
|
18937
19269
|
const path = pairRecordPath(ledger, task.id, pair);
|
|
18938
|
-
if (!
|
|
19270
|
+
if (!existsSync18(path)) continue;
|
|
18939
19271
|
const existing = resumableRecord(path, {
|
|
18940
19272
|
task: task.id,
|
|
18941
19273
|
pair,
|
|
@@ -19060,10 +19392,10 @@ var init_run2 = __esm({
|
|
|
19060
19392
|
});
|
|
19061
19393
|
|
|
19062
19394
|
// src/research/summarize.ts
|
|
19063
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
19064
|
-
import { join as
|
|
19395
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync24 } from "node:fs";
|
|
19396
|
+
import { join as join26, resolve as resolve16 } from "node:path";
|
|
19065
19397
|
function readLedger2(dir) {
|
|
19066
|
-
const pairsDir =
|
|
19398
|
+
const pairsDir = join26(resolve16(dir), "pairs");
|
|
19067
19399
|
let names;
|
|
19068
19400
|
try {
|
|
19069
19401
|
names = readdirSync7(pairsDir).filter((n) => n.endsWith(".json")).sort();
|
|
@@ -19072,10 +19404,10 @@ function readLedger2(dir) {
|
|
|
19072
19404
|
}
|
|
19073
19405
|
if (names.length === 0) throw new ResearchError(`ledger ${pairsDir} holds no pair records`);
|
|
19074
19406
|
return names.map((n) => {
|
|
19075
|
-
const path =
|
|
19407
|
+
const path = join26(pairsDir, n);
|
|
19076
19408
|
let raw;
|
|
19077
19409
|
try {
|
|
19078
|
-
raw = JSON.parse(
|
|
19410
|
+
raw = JSON.parse(readFileSync24(path, "utf8"));
|
|
19079
19411
|
} catch (e) {
|
|
19080
19412
|
throw new ResearchError(`ledger record ${path} is not valid JSON: ${errorMessage(e)}`);
|
|
19081
19413
|
}
|
|
@@ -19316,6 +19648,17 @@ function parseDoctor(args) {
|
|
|
19316
19648
|
}
|
|
19317
19649
|
return o;
|
|
19318
19650
|
}
|
|
19651
|
+
function parseStats(args) {
|
|
19652
|
+
const o = {};
|
|
19653
|
+
for (let i = 0; i < args.length; i++) {
|
|
19654
|
+
const a = args[i];
|
|
19655
|
+
if (a === "--cwd") o.cwd = args[++i];
|
|
19656
|
+
else if (a === "--file") o.file = args[++i];
|
|
19657
|
+
else if (a === "--since") o.since = args[++i];
|
|
19658
|
+
else if (a === "--json") o.json = true;
|
|
19659
|
+
}
|
|
19660
|
+
return o;
|
|
19661
|
+
}
|
|
19319
19662
|
function parseCheck(args) {
|
|
19320
19663
|
const o = {};
|
|
19321
19664
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -19455,6 +19798,16 @@ function validateCliArgs(cmd, args) {
|
|
|
19455
19798
|
}
|
|
19456
19799
|
}).error;
|
|
19457
19800
|
}
|
|
19801
|
+
if (cmd === "stats") {
|
|
19802
|
+
return validateFlatArgs(args, {
|
|
19803
|
+
flags: ["--json"],
|
|
19804
|
+
values: {
|
|
19805
|
+
"--cwd": "string",
|
|
19806
|
+
"--file": "string",
|
|
19807
|
+
"--since": "string"
|
|
19808
|
+
}
|
|
19809
|
+
}).error;
|
|
19810
|
+
}
|
|
19458
19811
|
if (cmd === "onboard") {
|
|
19459
19812
|
const parsed = validateFlatArgs(args, {
|
|
19460
19813
|
flags: ["--yes", "--skip-demo", "--demo", "--no-github"],
|
|
@@ -19615,6 +19968,12 @@ Formats:
|
|
|
19615
19968
|
tamperward research summarize --ledger D aggregate measured pairs into model behaviour,
|
|
19616
19969
|
independent outcome, TamperWard hits/misses
|
|
19617
19970
|
and paired counts \u2014 no composite score
|
|
19971
|
+
tamperward stats [--file F] [--since 30d] aggregate privacy-safe hook/sweep audit
|
|
19972
|
+
[--json] [--cwd D] events by rule and enforcement surface.
|
|
19973
|
+
Defaults to the repository-local
|
|
19974
|
+
.git/tamperward/audit.jsonl (or
|
|
19975
|
+
TAMPERWARD_AUDIT_LOG). A finding is an
|
|
19976
|
+
integrity signal, not proof of agent intent.
|
|
19618
19977
|
tamperward allow <rule> --reason "..." record a human sign-off (local audit ledger)
|
|
19619
19978
|
[--file F] [--cwd D]
|
|
19620
19979
|
tamperward onboard [--yes] [--cwd D] guided first-run setup: preflight, the
|
|
@@ -19685,6 +20044,8 @@ function main(argv) {
|
|
|
19685
20044
|
return runInit(parseInit(rest));
|
|
19686
20045
|
case "doctor":
|
|
19687
20046
|
return runDoctor(parseDoctor(rest));
|
|
20047
|
+
case "stats":
|
|
20048
|
+
return runStats(parseStats(rest));
|
|
19688
20049
|
case "onboard":
|
|
19689
20050
|
return runOnboard(parseOnboard(rest));
|
|
19690
20051
|
case "watch":
|
|
@@ -19746,13 +20107,14 @@ var init_main = __esm({
|
|
|
19746
20107
|
init_watch();
|
|
19747
20108
|
init_onboard();
|
|
19748
20109
|
init_research();
|
|
20110
|
+
init_audit();
|
|
19749
20111
|
}
|
|
19750
20112
|
});
|
|
19751
20113
|
|
|
19752
20114
|
// src/cli/index.ts
|
|
19753
20115
|
init_hook_client();
|
|
19754
20116
|
init_exit();
|
|
19755
|
-
import { readFileSync as
|
|
20117
|
+
import { readFileSync as readFileSync25 } from "node:fs";
|
|
19756
20118
|
function loadMain() {
|
|
19757
20119
|
return Promise.resolve().then(() => (init_main(), main_exports));
|
|
19758
20120
|
}
|
|
@@ -19765,7 +20127,7 @@ async function launch(argv) {
|
|
|
19765
20127
|
if (kind && hookServiceEnabled()) {
|
|
19766
20128
|
let raw = null;
|
|
19767
20129
|
try {
|
|
19768
|
-
raw =
|
|
20130
|
+
raw = readFileSync25(0, "utf8");
|
|
19769
20131
|
} catch {
|
|
19770
20132
|
raw = null;
|
|
19771
20133
|
}
|