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 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/index.ts`,
340
- `parseVerify`, `parseRun`). `check` reports an unknown flag on stderr; the other
341
- commands ignore what they do not know.
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 code = errCode(e);
312
- if (code === "ENOENT" || code === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
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 code = errCode(e);
324
- if (code === "ENOENT" || code === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
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 code = errCode(e);
338
- if (code === "ENOENT" || code === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
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 code = files.filter((f) => langOf(f) !== null);
2673
- return code.length ? code : CODE_PROBES;
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, code, on) {
6539
- return on ? code + s + RESET : s;
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, statSync, watch } from "node:fs";
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 (statSync(join11(dir, child)).isDirectory()) addDir(child);
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 (statSync(join11(dir, child)).isDirectory()) walk2(child);
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 as statSync2 } from "node:fs";
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 = statSync2(log).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 statSync3 } from "node:fs";
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) && !statSync3(path).isFile()) {
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 lstatSync4, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync3, symlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
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 lstatSync3,
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 statSync4
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 = lstatSync3(path);
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 = lstatSync3(root.path);
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 (!statSync4(root.path).isDirectory()) {
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 = statSync4(path);
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 = statSync4(path);
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 (!statSync4(path).isDirectory()) {
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) && statSync4(candidate).isFile()) return realpathSync3(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 statSync5 } from "node:fs";
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 = statSync5(path);
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 code = r.error.code;
9321
- return code === "ETIMEDOUT" ? { exit: null, secs, failure: "budget", reason: "verifier stage exceeded its budget" } : { exit: null, secs, failure: "backend", reason: `Docker client failed: ${r.error.message}` };
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 = lstatSync4(current);
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 = lstatSync4(current);
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 = lstatSync4(src);
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 (lstatSync4(p).isSymbolicLink()) rmSync3(p, { force: true });
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 = lstatSync4(p);
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 code;
9945
+ let code2;
9946
9946
  if (visible.exit === null || pristine.exit === null) {
9947
9947
  verdict2 = "BUDGET_EXCEEDED";
9948
- code = 2;
9948
+ code2 = 2;
9949
9949
  } else if (visible.exit === 0 && pristine.exit === 0) {
9950
9950
  verdict2 = "VERIFIED";
9951
- code = 0;
9951
+ code2 = 0;
9952
9952
  } else if (visible.exit === 0) {
9953
9953
  verdict2 = "MASKED_FAILURE";
9954
- code = 1;
9954
+ code2 = 1;
9955
9955
  } else {
9956
9956
  verdict2 = "SUITE_RED";
9957
- code = 1;
9957
+ code2 = 1;
9958
9958
  }
9959
9959
  const signedOff = verdict2 === "MASKED_FAILURE" ? oobToken("verify", oobFromEnv(), oobHeadFromEnv()) : null;
9960
- if (signedOff) code = 0;
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 code;
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
- if (argv[0] === "watch") {
10698
- runWatch(argv.slice(1));
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.0",
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",