tamperward 2.13.0 → 2.13.2
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 +7 -3
- package/dist/cli/index.js +192 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -336,9 +336,13 @@ from trusted CI, or after an externally isolated agent hands off the frozen cand
|
|
|
336
336
|
|
|
337
337
|
### CLI reference
|
|
338
338
|
|
|
339
|
-
Every flag below is what the command's parser actually reads (`src/cli/
|
|
340
|
-
`parseVerify`, `parseRun`).
|
|
341
|
-
|
|
339
|
+
Every flag below is what the command's parser actually reads (`src/cli/main.ts`,
|
|
340
|
+
`parseVerify`, `parseRun`). Since **2.13.1**, user-supplied argv is validated before
|
|
341
|
+
any command-specific parser or side effect runs: unknown options, missing values,
|
|
342
|
+
invalid numeric budgets/timeouts, duplicate/conflicting `check` views, and unexpected
|
|
343
|
+
positionals fail closed with exit 2 and one deterministic `tamperward: ...` diagnostic.
|
|
344
|
+
`run` requires an explicit `--` before the wrapped command, so a typoed TamperWard
|
|
345
|
+
option can never be reinterpreted as the agent command.
|
|
342
346
|
|
|
343
347
|
| command | flags |
|
|
344
348
|
| --- | --- |
|
package/dist/cli/index.js
CHANGED
|
@@ -308,8 +308,8 @@ function inspectOnce(abs2) {
|
|
|
308
308
|
try {
|
|
309
309
|
st = lstatSync(abs2);
|
|
310
310
|
} catch (e) {
|
|
311
|
-
const
|
|
312
|
-
if (
|
|
311
|
+
const code2 = errCode(e);
|
|
312
|
+
if (code2 === "ENOENT" || code2 === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
|
|
313
313
|
return { kind: "unreadable", content: null, ...NO_META, detail: errText(e) };
|
|
314
314
|
}
|
|
315
315
|
if (st.isSymbolicLink() || !st.isFile()) return classify(abs2, st);
|
|
@@ -320,8 +320,8 @@ function inspectPath(abs2) {
|
|
|
320
320
|
try {
|
|
321
321
|
st = lstatSync(abs2);
|
|
322
322
|
} catch (e) {
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
323
|
+
const code2 = errCode(e);
|
|
324
|
+
if (code2 === "ENOENT" || code2 === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
|
|
325
325
|
return { kind: "unreadable", content: null, ...NO_META, detail: errText(e) };
|
|
326
326
|
}
|
|
327
327
|
return classify(abs2, st);
|
|
@@ -334,8 +334,8 @@ function inspectResolved(abs2) {
|
|
|
334
334
|
try {
|
|
335
335
|
real = realpathSync(abs2);
|
|
336
336
|
} catch (e) {
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
337
|
+
const code2 = errCode(e);
|
|
338
|
+
if (code2 === "ENOENT" || code2 === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
|
|
339
339
|
return { kind: "unreadable", content: null, ...NO_META, detail: errText(e) };
|
|
340
340
|
}
|
|
341
341
|
return inspectPath(real);
|
|
@@ -2669,8 +2669,8 @@ var CODE_PROBES = ["src/index.ts", "src/index.js", "lib/index.js", "index.ts", "
|
|
|
2669
2669
|
function sourceProbes(ctx) {
|
|
2670
2670
|
const files = trackedFiles(ctx);
|
|
2671
2671
|
if (!files) return CODE_PROBES;
|
|
2672
|
-
const
|
|
2673
|
-
return
|
|
2672
|
+
const code2 = files.filter((f) => langOf(f) !== null);
|
|
2673
|
+
return code2.length ? code2 : CODE_PROBES;
|
|
2674
2674
|
}
|
|
2675
2675
|
function triggerNarrowings(before, after, opts) {
|
|
2676
2676
|
const out2 = [];
|
|
@@ -6535,8 +6535,8 @@ function terminalWidth(stream = process.stdout) {
|
|
|
6535
6535
|
if (!c || c < 40) return 80;
|
|
6536
6536
|
return Math.min(c, 100);
|
|
6537
6537
|
}
|
|
6538
|
-
function paint(s,
|
|
6539
|
-
return on ?
|
|
6538
|
+
function paint(s, code2, on) {
|
|
6539
|
+
return on ? code2 + s + RESET : s;
|
|
6540
6540
|
}
|
|
6541
6541
|
function wrap(text, width) {
|
|
6542
6542
|
const out2 = [];
|
|
@@ -7025,7 +7025,7 @@ function dropTurnTree(cwd, sessionId) {
|
|
|
7025
7025
|
}
|
|
7026
7026
|
|
|
7027
7027
|
// src/cli/watch.ts
|
|
7028
|
-
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2,
|
|
7028
|
+
import { appendFileSync as appendFileSync3, lstatSync as lstatSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, watch } from "node:fs";
|
|
7029
7029
|
import { join as join11 } from "node:path";
|
|
7030
7030
|
import { createHash as createHash4 } from "node:crypto";
|
|
7031
7031
|
var SKIP2 = /(^|\/)(\.git|node_modules)(\/|$)/;
|
|
@@ -7060,7 +7060,7 @@ function watchTree(dir, cb) {
|
|
|
7060
7060
|
const child = rel ? `${rel}/${String(fname)}` : String(fname);
|
|
7061
7061
|
cb(kind, child);
|
|
7062
7062
|
try {
|
|
7063
|
-
if (
|
|
7063
|
+
if (lstatSync3(join11(dir, child)).isDirectory()) addDir(child);
|
|
7064
7064
|
} catch {
|
|
7065
7065
|
}
|
|
7066
7066
|
});
|
|
@@ -7081,7 +7081,7 @@ function watchTree(dir, cb) {
|
|
|
7081
7081
|
const child = rel ? `${rel}/${name}` : name;
|
|
7082
7082
|
if (SKIP2.test(child + "/")) continue;
|
|
7083
7083
|
try {
|
|
7084
|
-
if (
|
|
7084
|
+
if (lstatSync3(join11(dir, child)).isDirectory()) walk2(child);
|
|
7085
7085
|
} catch {
|
|
7086
7086
|
}
|
|
7087
7087
|
}
|
|
@@ -7126,12 +7126,12 @@ function runWatch(args) {
|
|
|
7126
7126
|
}
|
|
7127
7127
|
|
|
7128
7128
|
// src/detectors/fs-events.ts
|
|
7129
|
-
import { readFileSync as readFileSync10, statSync
|
|
7129
|
+
import { readFileSync as readFileSync10, statSync } from "node:fs";
|
|
7130
7130
|
var RULE11 = "transient-protected-mutation";
|
|
7131
7131
|
function readEvents(log, offset) {
|
|
7132
7132
|
let size = 0;
|
|
7133
7133
|
try {
|
|
7134
|
-
size =
|
|
7134
|
+
size = statSync(log).size;
|
|
7135
7135
|
} catch {
|
|
7136
7136
|
return { events: [], newOffset: offset };
|
|
7137
7137
|
}
|
|
@@ -7598,7 +7598,7 @@ Honored at LOCAL pre-commit only. The agent-layer hook ignores this file; CI req
|
|
|
7598
7598
|
// src/cli/init.ts
|
|
7599
7599
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
7600
7600
|
import { createHash as createHash6 } from "node:crypto";
|
|
7601
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync5, chmodSync, statSync as
|
|
7601
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync5, chmodSync, statSync as statSync2 } from "node:fs";
|
|
7602
7602
|
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute8, join as join12, relative as relative3, resolve as resolve3 } from "node:path";
|
|
7603
7603
|
|
|
7604
7604
|
// src/verifier-limits.ts
|
|
@@ -7985,7 +7985,7 @@ function planPreCommit(cwd) {
|
|
|
7985
7985
|
}
|
|
7986
7986
|
}
|
|
7987
7987
|
const rel = display(cwd, path);
|
|
7988
|
-
if (existsSync9(path) && !
|
|
7988
|
+
if (existsSync9(path) && !statSync2(path).isFile()) {
|
|
7989
7989
|
return { item: "pre-commit", path: rel, status: "error", detail: "exists but is not a regular file \u2014 refusing to write" };
|
|
7990
7990
|
}
|
|
7991
7991
|
const existing = existsSync9(path) ? readFileSync12(path, "utf8") : null;
|
|
@@ -8535,7 +8535,7 @@ function runDoctor(opts = {}) {
|
|
|
8535
8535
|
// src/cli/verify.ts
|
|
8536
8536
|
import { execFileSync as execFileSync9, spawnSync as spawnSync2 } from "node:child_process";
|
|
8537
8537
|
import { createHash as createHash10 } from "node:crypto";
|
|
8538
|
-
import { chmodSync as chmodSync2, cpSync, lstatSync as
|
|
8538
|
+
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync5, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync3, symlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8539
8539
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8540
8540
|
import { dirname as dirname7, isAbsolute as isAbsolute10, join as join16, relative as relative5, resolve as resolve7, sep as sep2 } from "node:path";
|
|
8541
8541
|
|
|
@@ -8569,12 +8569,12 @@ function treeFingerprint(cwd, keepIgnored) {
|
|
|
8569
8569
|
import { createHash as createHash8 } from "node:crypto";
|
|
8570
8570
|
import {
|
|
8571
8571
|
existsSync as existsSync11,
|
|
8572
|
-
lstatSync as
|
|
8572
|
+
lstatSync as lstatSync4,
|
|
8573
8573
|
readdirSync as readdirSync4,
|
|
8574
8574
|
readFileSync as readFileSync14,
|
|
8575
8575
|
readlinkSync as readlinkSync2,
|
|
8576
8576
|
realpathSync as realpathSync3,
|
|
8577
|
-
statSync as
|
|
8577
|
+
statSync as statSync3
|
|
8578
8578
|
} from "node:fs";
|
|
8579
8579
|
import {
|
|
8580
8580
|
basename as basename3,
|
|
@@ -8593,7 +8593,7 @@ function inside(root, candidate) {
|
|
|
8593
8593
|
return rel === "" || rel !== ".." && !rel.startsWith(".." + sep) && !isAbsolute9(rel);
|
|
8594
8594
|
}
|
|
8595
8595
|
function modeKind(path) {
|
|
8596
|
-
const st =
|
|
8596
|
+
const st = lstatSync4(path);
|
|
8597
8597
|
if (st.isFile()) return { kind: "file", mode: st.mode };
|
|
8598
8598
|
if (st.isDirectory()) return { kind: "dir", mode: st.mode };
|
|
8599
8599
|
if (st.isSymbolicLink()) return { kind: "symlink", mode: st.mode };
|
|
@@ -8603,7 +8603,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
8603
8603
|
let rootStat;
|
|
8604
8604
|
let currentReal;
|
|
8605
8605
|
try {
|
|
8606
|
-
rootStat =
|
|
8606
|
+
rootStat = lstatSync4(root.path);
|
|
8607
8607
|
currentReal = realpathSync3(root.path);
|
|
8608
8608
|
} catch (e) {
|
|
8609
8609
|
throw new Unattestable(
|
|
@@ -8615,7 +8615,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
8615
8615
|
root.kind + " root moved from " + JSON.stringify(root.realPath) + " to " + JSON.stringify(currentReal)
|
|
8616
8616
|
);
|
|
8617
8617
|
}
|
|
8618
|
-
if (!
|
|
8618
|
+
if (!statSync3(root.path).isDirectory()) {
|
|
8619
8619
|
throw new Unattestable(root.kind + " root " + JSON.stringify(root.path) + " is not a directory");
|
|
8620
8620
|
}
|
|
8621
8621
|
h.update("root\0" + root.kind + "\0" + root.path + "\0" + root.realPath + "\0" + String(rootStat.mode) + "\0");
|
|
@@ -8625,7 +8625,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
8625
8625
|
const hashFile = (path, label) => {
|
|
8626
8626
|
let st;
|
|
8627
8627
|
try {
|
|
8628
|
-
st =
|
|
8628
|
+
st = statSync3(path);
|
|
8629
8629
|
if (!st.isFile()) throw new Error("not a regular file");
|
|
8630
8630
|
h.update(label + "\0external-file\0" + path + "\0" + String(st.mode) + "\0");
|
|
8631
8631
|
h.update(readFileSync14(path));
|
|
@@ -8691,7 +8691,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
8691
8691
|
} catch {
|
|
8692
8692
|
continue;
|
|
8693
8693
|
}
|
|
8694
|
-
const followed =
|
|
8694
|
+
const followed = statSync3(path);
|
|
8695
8695
|
if (followed.isFile()) {
|
|
8696
8696
|
hashFile(resolved, "@target:" + rel);
|
|
8697
8697
|
continue;
|
|
@@ -8729,7 +8729,7 @@ function snapshot(roots, probes, cwd) {
|
|
|
8729
8729
|
}
|
|
8730
8730
|
function freezeRoot(kind, path) {
|
|
8731
8731
|
try {
|
|
8732
|
-
if (!
|
|
8732
|
+
if (!statSync3(path).isDirectory()) {
|
|
8733
8733
|
throw new Error("not a directory");
|
|
8734
8734
|
}
|
|
8735
8735
|
return { kind, path: resolve5(path), realPath: realpathSync3(path) };
|
|
@@ -8778,7 +8778,7 @@ function resolveExecutable(token, cwd, env) {
|
|
|
8778
8778
|
for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
8779
8779
|
const candidate = resolve5(entry, token);
|
|
8780
8780
|
try {
|
|
8781
|
-
if (existsSync11(candidate) &&
|
|
8781
|
+
if (existsSync11(candidate) && statSync3(candidate).isFile()) return realpathSync3(candidate);
|
|
8782
8782
|
} catch {
|
|
8783
8783
|
}
|
|
8784
8784
|
}
|
|
@@ -8921,7 +8921,7 @@ function dependencyEnvironmentSummary(descriptor) {
|
|
|
8921
8921
|
// src/verifier-backend.ts
|
|
8922
8922
|
import { createHash as createHash9, randomUUID } from "node:crypto";
|
|
8923
8923
|
import { spawnSync } from "node:child_process";
|
|
8924
|
-
import { accessSync, constants as constants2, readFileSync as readFileSync15, realpathSync as realpathSync4, statSync as
|
|
8924
|
+
import { accessSync, constants as constants2, readFileSync as readFileSync15, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
|
|
8925
8925
|
import { delimiter as delimiter2, dirname as dirname6, join as join15, resolve as resolve6 } from "node:path";
|
|
8926
8926
|
var DEFAULT_CONTAINER_RESOURCE_LIMITS = Object.freeze({
|
|
8927
8927
|
memoryBytes: 2 * 1024 * 1024 * 1024,
|
|
@@ -8939,7 +8939,7 @@ function identity() {
|
|
|
8939
8939
|
}
|
|
8940
8940
|
function writableByCaller(path) {
|
|
8941
8941
|
try {
|
|
8942
|
-
const st =
|
|
8942
|
+
const st = statSync4(path);
|
|
8943
8943
|
const { uid, groups } = identity();
|
|
8944
8944
|
if (uid === 0) return true;
|
|
8945
8945
|
if ((st.mode & 2) !== 0) return true;
|
|
@@ -9317,8 +9317,8 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
9317
9317
|
const secs = Math.round((Date.now() - t0) / 1e3);
|
|
9318
9318
|
if (r.error) {
|
|
9319
9319
|
cleanupContainer(backend, name);
|
|
9320
|
-
const
|
|
9321
|
-
return
|
|
9320
|
+
const code2 = r.error.code;
|
|
9321
|
+
return code2 === "ETIMEDOUT" ? { exit: null, secs, failure: "budget", reason: "verifier stage exceeded its budget" } : { exit: null, secs, failure: "backend", reason: `Docker client failed: ${r.error.message}` };
|
|
9322
9322
|
}
|
|
9323
9323
|
if (r.status === null) {
|
|
9324
9324
|
cleanupContainer(backend, name);
|
|
@@ -9512,7 +9512,7 @@ function validateSymlinkGraph(target, out2, root, dependencyRoot, label) {
|
|
|
9512
9512
|
const current = join16(base, ...stack);
|
|
9513
9513
|
let st;
|
|
9514
9514
|
try {
|
|
9515
|
-
st =
|
|
9515
|
+
st = lstatSync5(current);
|
|
9516
9516
|
} catch {
|
|
9517
9517
|
continue;
|
|
9518
9518
|
}
|
|
@@ -9548,7 +9548,7 @@ function rejectLinkedParent(cwd, rel) {
|
|
|
9548
9548
|
current = join16(current, part);
|
|
9549
9549
|
let st;
|
|
9550
9550
|
try {
|
|
9551
|
-
st =
|
|
9551
|
+
st = lstatSync5(current);
|
|
9552
9552
|
} catch {
|
|
9553
9553
|
continue;
|
|
9554
9554
|
}
|
|
@@ -9565,7 +9565,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
9565
9565
|
rejectLinkedParent(cwd, rel);
|
|
9566
9566
|
let st;
|
|
9567
9567
|
try {
|
|
9568
|
-
st =
|
|
9568
|
+
st = lstatSync5(src);
|
|
9569
9569
|
} catch {
|
|
9570
9570
|
continue;
|
|
9571
9571
|
}
|
|
@@ -9590,7 +9590,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
9590
9590
|
}
|
|
9591
9591
|
function dropSymlink(p) {
|
|
9592
9592
|
try {
|
|
9593
|
-
if (
|
|
9593
|
+
if (lstatSync5(p).isSymbolicLink()) rmSync3(p, { force: true });
|
|
9594
9594
|
} catch {
|
|
9595
9595
|
}
|
|
9596
9596
|
}
|
|
@@ -9621,7 +9621,7 @@ function overlayDigest(dest, paths) {
|
|
|
9621
9621
|
h.update("\0");
|
|
9622
9622
|
const p = join16(dest, rel);
|
|
9623
9623
|
try {
|
|
9624
|
-
const st =
|
|
9624
|
+
const st = lstatSync5(p);
|
|
9625
9625
|
h.update(String(st.mode));
|
|
9626
9626
|
h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync16(p));
|
|
9627
9627
|
} catch {
|
|
@@ -9942,22 +9942,22 @@ function runVerify(opts) {
|
|
|
9942
9942
|
return 2;
|
|
9943
9943
|
}
|
|
9944
9944
|
let verdict2;
|
|
9945
|
-
let
|
|
9945
|
+
let code2;
|
|
9946
9946
|
if (visible.exit === null || pristine.exit === null) {
|
|
9947
9947
|
verdict2 = "BUDGET_EXCEEDED";
|
|
9948
|
-
|
|
9948
|
+
code2 = 2;
|
|
9949
9949
|
} else if (visible.exit === 0 && pristine.exit === 0) {
|
|
9950
9950
|
verdict2 = "VERIFIED";
|
|
9951
|
-
|
|
9951
|
+
code2 = 0;
|
|
9952
9952
|
} else if (visible.exit === 0) {
|
|
9953
9953
|
verdict2 = "MASKED_FAILURE";
|
|
9954
|
-
|
|
9954
|
+
code2 = 1;
|
|
9955
9955
|
} else {
|
|
9956
9956
|
verdict2 = "SUITE_RED";
|
|
9957
|
-
|
|
9957
|
+
code2 = 1;
|
|
9958
9958
|
}
|
|
9959
9959
|
const signedOff = verdict2 === "MASKED_FAILURE" ? oobToken("verify", oobFromEnv(), oobHeadFromEnv()) : null;
|
|
9960
|
-
if (signedOff)
|
|
9960
|
+
if (signedOff) code2 = 0;
|
|
9961
9961
|
if (opts.json) {
|
|
9962
9962
|
out2(
|
|
9963
9963
|
JSON.stringify({
|
|
@@ -10000,7 +10000,7 @@ function runVerify(opts) {
|
|
|
10000
10000
|
`(${removedAdded} protected file(s) added since ${base.slice(0, 10)} were removed from the pristine run: the pristine tree carries exactly the base's protected surface.)`
|
|
10001
10001
|
);
|
|
10002
10002
|
}
|
|
10003
|
-
return
|
|
10003
|
+
return code2;
|
|
10004
10004
|
}
|
|
10005
10005
|
function parseVerify(args) {
|
|
10006
10006
|
const o = {};
|
|
@@ -10567,17 +10567,148 @@ function parseCheck(args) {
|
|
|
10567
10567
|
else if (a === "--format") {
|
|
10568
10568
|
const v = args[++i];
|
|
10569
10569
|
if (v !== void 0 && isFormat(v)) o.format = v;
|
|
10570
|
-
else process.stderr.write(`tamperward: unknown --format "${v ?? ""}" (expected ${FORMATS.join(" | ")})
|
|
10571
|
-
`);
|
|
10572
10570
|
} else if (a === "--diff") o.diff = args[++i];
|
|
10573
10571
|
else if (a === "--cwd") o.cwd = args[++i];
|
|
10574
|
-
else {
|
|
10575
|
-
process.stderr.write(`tamperward: unknown flag "${a}"
|
|
10576
|
-
`);
|
|
10577
|
-
}
|
|
10578
10572
|
}
|
|
10579
10573
|
return o;
|
|
10580
10574
|
}
|
|
10575
|
+
function validateFlatArgs(args, grammar) {
|
|
10576
|
+
const flags = new Set(grammar.flags ?? []);
|
|
10577
|
+
const values = grammar.values ?? {};
|
|
10578
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10579
|
+
const positionals = [];
|
|
10580
|
+
for (let i = 0; i < args.length; i++) {
|
|
10581
|
+
const a = args[i];
|
|
10582
|
+
if (flags.has(a) || Object.prototype.hasOwnProperty.call(values, a)) {
|
|
10583
|
+
if (seen.has(a)) {
|
|
10584
|
+
return { error: `option "${a}" specified more than once`, seen, positionals };
|
|
10585
|
+
}
|
|
10586
|
+
seen.add(a);
|
|
10587
|
+
const rule = values[a];
|
|
10588
|
+
if (!rule) continue;
|
|
10589
|
+
const v = args[i + 1];
|
|
10590
|
+
if (v === void 0 || v.startsWith("--")) {
|
|
10591
|
+
return {
|
|
10592
|
+
error: `${a} needs a value` + (v === void 0 ? "" : ` (got the flag "${v}")`),
|
|
10593
|
+
seen,
|
|
10594
|
+
positionals
|
|
10595
|
+
};
|
|
10596
|
+
}
|
|
10597
|
+
i++;
|
|
10598
|
+
if (rule === "positive") {
|
|
10599
|
+
const n = Number(v);
|
|
10600
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
10601
|
+
return { error: `${a} needs a positive number (got "${v}")`, seen, positionals };
|
|
10602
|
+
}
|
|
10603
|
+
} else if (rule === "non-negative") {
|
|
10604
|
+
const n = Number(v);
|
|
10605
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
10606
|
+
return { error: `${a} needs a non-negative number (got "${v}")`, seen, positionals };
|
|
10607
|
+
}
|
|
10608
|
+
} else if (rule === "format" && !isFormat(v)) {
|
|
10609
|
+
return {
|
|
10610
|
+
error: `--format needs one of ${FORMATS.join(" | ")} (got "${v}")`,
|
|
10611
|
+
seen,
|
|
10612
|
+
positionals
|
|
10613
|
+
};
|
|
10614
|
+
}
|
|
10615
|
+
continue;
|
|
10616
|
+
}
|
|
10617
|
+
if (a.startsWith("-")) {
|
|
10618
|
+
return { error: `unknown option "${a}"`, seen, positionals };
|
|
10619
|
+
}
|
|
10620
|
+
if (grammar.positional === "one" && positionals.length === 0) {
|
|
10621
|
+
positionals.push(a);
|
|
10622
|
+
continue;
|
|
10623
|
+
}
|
|
10624
|
+
return { error: `unexpected argument "${a}"`, seen, positionals };
|
|
10625
|
+
}
|
|
10626
|
+
return { seen, positionals };
|
|
10627
|
+
}
|
|
10628
|
+
function validateCliArgs(cmd, args) {
|
|
10629
|
+
if (cmd === "hook" || cmd === "sweep") {
|
|
10630
|
+
if (args.length === 0) return `${cmd} requires an agent name`;
|
|
10631
|
+
if (args.length > 1) return `unexpected argument "${args[1]}"`;
|
|
10632
|
+
return void 0;
|
|
10633
|
+
}
|
|
10634
|
+
if (cmd === "run") {
|
|
10635
|
+
const delimiter3 = args.indexOf("--");
|
|
10636
|
+
if (delimiter3 < 0) return 'run requires an explicit "--" before the wrapped command';
|
|
10637
|
+
const prefix = args.slice(0, delimiter3);
|
|
10638
|
+
const command = args.slice(delimiter3 + 1);
|
|
10639
|
+
const parsed = validateFlatArgs(prefix, {
|
|
10640
|
+
flags: ["--allow-dirty", "--allow-dep-drift"],
|
|
10641
|
+
values: {
|
|
10642
|
+
"--base": "string",
|
|
10643
|
+
"--cmd": "string",
|
|
10644
|
+
"--budget": "positive",
|
|
10645
|
+
"--agent-budget": "positive",
|
|
10646
|
+
"--settle": "non-negative",
|
|
10647
|
+
"--cwd": "string"
|
|
10648
|
+
}
|
|
10649
|
+
});
|
|
10650
|
+
if (parsed.error) return parsed.error;
|
|
10651
|
+
if (command.length === 0) return 'run requires a wrapped command after "--"';
|
|
10652
|
+
return void 0;
|
|
10653
|
+
}
|
|
10654
|
+
if (cmd === "check") {
|
|
10655
|
+
const parsed = validateFlatArgs(args, {
|
|
10656
|
+
flags: ["--staged", "--worktree", "--json"],
|
|
10657
|
+
values: { "--diff": "string", "--format": "format", "--cwd": "string" }
|
|
10658
|
+
});
|
|
10659
|
+
if (parsed.error) return parsed.error;
|
|
10660
|
+
const views = ["--staged", "--worktree", "--diff"].filter((x) => parsed.seen.has(x));
|
|
10661
|
+
if (views.length > 1) return "choose exactly one of --staged, --worktree, or --diff";
|
|
10662
|
+
if (parsed.seen.has("--json") && parsed.seen.has("--format")) {
|
|
10663
|
+
return "--json cannot be combined with --format";
|
|
10664
|
+
}
|
|
10665
|
+
return void 0;
|
|
10666
|
+
}
|
|
10667
|
+
if (cmd === "allow") {
|
|
10668
|
+
const parsed = validateFlatArgs(args, {
|
|
10669
|
+
values: { "--file": "string", "--reason": "string", "--cwd": "string" },
|
|
10670
|
+
positional: "one"
|
|
10671
|
+
});
|
|
10672
|
+
if (parsed.error) return parsed.error;
|
|
10673
|
+
if (parsed.positionals.length === 0) return "allow requires a rule";
|
|
10674
|
+
return void 0;
|
|
10675
|
+
}
|
|
10676
|
+
if (cmd === "init") {
|
|
10677
|
+
return validateFlatArgs(args, {
|
|
10678
|
+
flags: ["--dry-run", "--force-workflow"],
|
|
10679
|
+
values: { "--cwd": "string" }
|
|
10680
|
+
}).error;
|
|
10681
|
+
}
|
|
10682
|
+
if (cmd === "doctor") {
|
|
10683
|
+
return validateFlatArgs(args, {
|
|
10684
|
+
flags: ["--github"],
|
|
10685
|
+
values: {
|
|
10686
|
+
"--cwd": "string",
|
|
10687
|
+
"--base": "string",
|
|
10688
|
+
"--workflow": "string",
|
|
10689
|
+
"--repo": "string",
|
|
10690
|
+
"--branch": "string"
|
|
10691
|
+
}
|
|
10692
|
+
}).error;
|
|
10693
|
+
}
|
|
10694
|
+
if (cmd === "verify") {
|
|
10695
|
+
return validateFlatArgs(args, {
|
|
10696
|
+
flags: ["--json", "--keep", "--require-ancestor"],
|
|
10697
|
+
values: {
|
|
10698
|
+
"--base": "string",
|
|
10699
|
+
"--cmd": "string",
|
|
10700
|
+
"--cwd": "string",
|
|
10701
|
+
"--budget": "positive"
|
|
10702
|
+
}
|
|
10703
|
+
}).error;
|
|
10704
|
+
}
|
|
10705
|
+
if (cmd === "watch") {
|
|
10706
|
+
return validateFlatArgs(args, {
|
|
10707
|
+
values: { "--dir": "string", "--log": "string" }
|
|
10708
|
+
}).error;
|
|
10709
|
+
}
|
|
10710
|
+
return void 0;
|
|
10711
|
+
}
|
|
10581
10712
|
function printHelp() {
|
|
10582
10713
|
process.stdout.write(`tamperward \u2014 the deterministic agent-integrity gate
|
|
10583
10714
|
|
|
@@ -10652,6 +10783,14 @@ Exit codes: 0 clean \xB7 1 a blocking finding (check), MASKED_FAILURE or SUITE_R
|
|
|
10652
10783
|
}
|
|
10653
10784
|
function main(argv2) {
|
|
10654
10785
|
const [cmd, ...rest] = argv2;
|
|
10786
|
+
if (cmd !== void 0 && cmd !== "-h" && cmd !== "--help") {
|
|
10787
|
+
const invalid = validateCliArgs(cmd, rest);
|
|
10788
|
+
if (invalid) {
|
|
10789
|
+
process.stderr.write(`tamperward: ${invalid}
|
|
10790
|
+
`);
|
|
10791
|
+
return 2;
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
10655
10794
|
switch (cmd) {
|
|
10656
10795
|
case "check":
|
|
10657
10796
|
return runCheck(parseCheck(rest));
|
|
@@ -10665,6 +10804,8 @@ function main(argv2) {
|
|
|
10665
10804
|
return runInit(parseInit(rest));
|
|
10666
10805
|
case "doctor":
|
|
10667
10806
|
return runDoctor(parseDoctor(rest));
|
|
10807
|
+
case "watch":
|
|
10808
|
+
return runWatch(rest);
|
|
10668
10809
|
case "verify":
|
|
10669
10810
|
return runVerify(parseVerify(rest));
|
|
10670
10811
|
case "run":
|
|
@@ -10694,8 +10835,5 @@ function guardedMain(argv2, stderr = process.stderr) {
|
|
|
10694
10835
|
|
|
10695
10836
|
// src/cli/index.ts
|
|
10696
10837
|
var argv = process.argv.slice(2);
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
} else {
|
|
10700
|
-
process.exit(guardedMain(argv));
|
|
10701
|
-
}
|
|
10838
|
+
var code = guardedMain(argv);
|
|
10839
|
+
if (code >= 0) process.exit(code);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.2",
|
|
4
4
|
"description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "hexrift",
|