tamperward 2.29.11 → 2.29.13

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.
Files changed (3) hide show
  1. package/README.md +173 -676
  2. package/dist/cli/index.js +144 -42
  3. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -3663,32 +3663,57 @@ function kindOfType(type, resolveName) {
3663
3663
  return null;
3664
3664
  }
3665
3665
  function buildAliasMap(sf) {
3666
- const decls = /* @__PURE__ */ new Map();
3666
+ const bindings = [];
3667
+ const shadow = (name, scope) => {
3668
+ if (scope) bindings.push({ kind: "shadow", name, scope });
3669
+ };
3667
3670
  const collect2 = (node) => {
3668
- if (ts.isTypeAliasDeclaration(node) && (!node.typeParameters || node.typeParameters.length === 0)) decls.set(node.name.text, node.type);
3671
+ if (ts.isTypeAliasDeclaration(node)) {
3672
+ if (!node.typeParameters || node.typeParameters.length === 0) {
3673
+ bindings.push({ kind: "alias", name: node.name.text, scope: node.parent, type: node.type });
3674
+ } else {
3675
+ shadow(node.name.text, node.parent);
3676
+ }
3677
+ } else if (ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node)) {
3678
+ shadow(node.name.text, node.parent);
3679
+ } else if ((ts.isClassDeclaration(node) || ts.isModuleDeclaration(node)) && node.name && ts.isIdentifier(node.name)) {
3680
+ shadow(node.name.text, node.parent);
3681
+ } else if (ts.isTypeParameterDeclaration(node) && ts.isIdentifier(node.name)) {
3682
+ shadow(node.name.text, node.parent);
3683
+ }
3669
3684
  ts.forEachChild(node, collect2);
3670
3685
  };
3671
3686
  collect2(sf);
3672
- const resolve17 = (name, seen) => {
3687
+ const kindOfBinding = (b, seen) => b.type ? kindOfType(b.type, (n) => resolveLexical(n, b.type, seen)) : null;
3688
+ const resolveLexical = (name, at, seen) => {
3673
3689
  if (seen.has(name)) return null;
3674
- seen.add(name);
3675
- const t = decls.get(name);
3676
- return t ? kindOfType(t, (n) => resolve17(n, seen)) : null;
3690
+ for (let n = at; n; n = n.parent) {
3691
+ const here = bindings.filter((x) => x.name === name && x.scope === n);
3692
+ if (here.length === 0) continue;
3693
+ if (here.some((b) => b.kind !== "alias" || !b.type)) return null;
3694
+ return kindOfBinding(here[0], new Set(seen).add(name));
3695
+ }
3696
+ return null;
3697
+ };
3698
+ const resolveFlat = (name, seen) => {
3699
+ if (seen.has(name)) return null;
3700
+ if (bindings.some((b) => b.name === name && (b.kind !== "alias" || !b.type))) return null;
3701
+ const aliases = bindings.filter((b) => b.kind === "alias" && b.name === name);
3702
+ if (aliases.length === 0) return null;
3703
+ const kinds = new Set(aliases.map((b) => kindOfBinding(b, new Set(seen).add(name))));
3704
+ return kinds.size === 1 ? [...kinds][0] ?? null : null;
3705
+ };
3706
+ return {
3707
+ resolve: (name, at) => at && at.getSourceFile() === sf ? resolveLexical(name, at, /* @__PURE__ */ new Set()) : resolveFlat(name, /* @__PURE__ */ new Set())
3677
3708
  };
3678
- const resolved = /* @__PURE__ */ new Map();
3679
- for (const name of decls.keys()) {
3680
- const k = resolve17(name, /* @__PURE__ */ new Set());
3681
- if (k) resolved.set(name, k);
3682
- }
3683
- return resolved;
3684
3709
  }
3685
- function assertedLaunderKind(node, aliasMap) {
3686
- return kindOfType(assertedType(node), (n) => aliasMap.get(n) ?? null);
3710
+ function assertedLaunderKind(node, aliases) {
3711
+ return kindOfType(assertedType(node), (n) => aliases.resolve(n, node));
3687
3712
  }
3688
- function isDoubleCast(node, aliasMap) {
3713
+ function isDoubleCast(node, aliases) {
3689
3714
  const inner = unparenthesized(node.expression);
3690
3715
  if (!(ts.isAsExpression(inner) || ts.isTypeAssertionExpression(inner))) return false;
3691
- const k = assertedLaunderKind(inner, aliasMap ?? EMPTY_ALIASES);
3716
+ const k = assertedLaunderKind(inner, aliases ?? EMPTY_ALIASES);
3692
3717
  return k != null && DOUBLE_CAST_INNER.has(k);
3693
3718
  }
3694
3719
  function surfaceOf(path, src) {
@@ -3767,7 +3792,7 @@ var init_ts_cast_growth = __esm({
3767
3792
  GENERATED_HEADER = /@generated\b|\bAUTO-?GENERATED\b|\bDO NOT EDIT\b/i;
3768
3793
  HEADER_LINES = 20;
3769
3794
  DOUBLE_CAST_INNER = /* @__PURE__ */ new Set(["any", "never", "unknown", "empty", "objectish"]);
3770
- EMPTY_ALIASES = /* @__PURE__ */ new Map();
3795
+ EMPTY_ALIASES = { resolve: () => null };
3771
3796
  tsCastGrowth = {
3772
3797
  id: RULE2,
3773
3798
  surface: ["file"],
@@ -3895,7 +3920,7 @@ function lineHasRow4Cast(line, aliasMap) {
3895
3920
  function aliasMapForAddedLines(c) {
3896
3921
  const snippet = addedLines(c).map((l) => l.content).join("\n");
3897
3922
  const sf = parseSource("lines.ts", snippet);
3898
- return sf ? buildAliasMap(sf) : /* @__PURE__ */ new Map();
3923
+ return sf ? buildAliasMap(sf) : EMPTY_ALIASES;
3899
3924
  }
3900
3925
  var BLOCK_RULE, WARN_RULE, JSDOC_ANY_CAST, JS_FILE, countMatches, DIRECTIVE_START, NARROW_LINE, BROAD_LINE, FRAGMENT_DIRECTIVE, BLOCK_REMEDIATION, WARN_REMEDIATION, tsAnyCast;
3901
3926
  var init_ts_any_cast = __esm({
@@ -15720,6 +15745,21 @@ function renderStageDiagnostics(out3, stage, result) {
15720
15745
  if (!result.diagnostics) return;
15721
15746
  for (const line of diagnosticLines(stage, result.diagnostics)) out3(line);
15722
15747
  }
15748
+ function verifyVerdictLine(verdict2, ctx) {
15749
+ const base10 = ctx.base.slice(0, 10);
15750
+ switch (verdict2) {
15751
+ case "VERIFIED":
15752
+ return `verified: the suite passes, and still passes with all ${ctx.restored} protected files restored from ${base10}.`;
15753
+ case "MASKED_FAILURE":
15754
+ return `MASKED FAILURE: the visible suite passes, but with the ${ctx.restored} protected files restored from ${base10} it FAILS (exit ${ctx.pristineExit}). Something weakened the checks; the code does not pass the original suite.`;
15755
+ case "SUITE_RED":
15756
+ return `suite red: the visible suite fails (exit ${ctx.visibleExit}) \u2014 fix the code first (pristine exit ${ctx.pristineExit}).`;
15757
+ case "BUDGET_EXCEEDED":
15758
+ return `budget exceeded (${ctx.budget}s): could not verify \u2014 failing closed, not open.`;
15759
+ default:
15760
+ return verdict2;
15761
+ }
15762
+ }
15723
15763
  function runVerify(opts) {
15724
15764
  const cwd = opts.cwd ?? process.cwd();
15725
15765
  const out3 = opts.silent ? (_s) => {
@@ -16046,13 +16086,15 @@ function runVerify(opts) {
16046
16086
  })
16047
16087
  );
16048
16088
  } else {
16049
- const lines = {
16050
- VERIFIED: `verified: the suite passes, and still passes with all ${restored.length} protected files restored from ${base.slice(0, 10)}.`,
16051
- MASKED_FAILURE: `MASKED FAILURE: the visible suite passes, but with the ${restored.length} protected files restored from ${base.slice(0, 10)} it FAILS (exit ${pristine.exit}). Something weakened the checks; the code does not pass the original suite.`,
16052
- SUITE_RED: `suite red: the visible suite fails (exit ${visible.exit}) \u2014 fix the code first (pristine exit ${pristine.exit}).`,
16053
- BUDGET_EXCEEDED: `budget exceeded (${budget}s): could not verify \u2014 failing closed, not open.`
16054
- };
16055
- out3(`tamperward verify \u2014 ${lines[verdict2]}`);
16089
+ out3(
16090
+ `tamperward verify \u2014 ${verifyVerdictLine(verdict2, {
16091
+ restored: restored.length,
16092
+ base,
16093
+ visibleExit: visible.exit,
16094
+ pristineExit: pristine.exit,
16095
+ budget
16096
+ })}`
16097
+ );
16056
16098
  out3(`verifier backend: ${verifierBackendSummary(verifierBackend)}`);
16057
16099
  out3(
16058
16100
  "oracle assurance: suite-exit-only (candidate source executes inside the suite process; execution-domain isolation is not semantic/oracle isolation)"
@@ -18195,18 +18237,69 @@ var init_trace_verify = __esm({
18195
18237
  });
18196
18238
 
18197
18239
  // src/runtimes.ts
18198
- import { existsSync as existsSync17 } from "node:fs";
18240
+ import { existsSync as existsSync17, readFileSync as readFileSync21 } from "node:fs";
18199
18241
  import { join as join23 } from "node:path";
18242
+ function isRecord4(x) {
18243
+ return typeof x === "object" && x !== null && !Array.isArray(x);
18244
+ }
18245
+ function hooksBeyondTamperward(hooks) {
18246
+ if (!isRecord4(hooks)) return hooks != null;
18247
+ for (const entries of Object.values(hooks)) {
18248
+ if (!Array.isArray(entries)) {
18249
+ if (entries != null) return true;
18250
+ continue;
18251
+ }
18252
+ for (const entry of entries) {
18253
+ const hs = isRecord4(entry) ? entry.hooks : null;
18254
+ if (!Array.isArray(hs)) {
18255
+ if (hs != null) return true;
18256
+ continue;
18257
+ }
18258
+ for (const h of hs) {
18259
+ const cmd = isRecord4(h) ? String(h.command ?? "") : "";
18260
+ if (!/tamperward/.test(cmd)) return true;
18261
+ }
18262
+ }
18263
+ }
18264
+ return false;
18265
+ }
18266
+ function claudeSettingsBeyondTamperward(cwd) {
18267
+ const path = join23(cwd, ".claude", "settings.json");
18268
+ let raw;
18269
+ try {
18270
+ if (!existsSync17(path)) return false;
18271
+ raw = readFileSync21(path, "utf8");
18272
+ } catch {
18273
+ return false;
18274
+ }
18275
+ let parsed;
18276
+ try {
18277
+ parsed = JSON.parse(raw);
18278
+ } catch {
18279
+ return raw.trim().length > 0;
18280
+ }
18281
+ if (!isRecord4(parsed)) return raw.trim().length > 0;
18282
+ for (const [key2, value] of Object.entries(parsed)) {
18283
+ if (key2 === "disableAllHooks") continue;
18284
+ if (key2 === "hooks") {
18285
+ if (hooksBeyondTamperward(value)) return true;
18286
+ continue;
18287
+ }
18288
+ return true;
18289
+ }
18290
+ return false;
18291
+ }
18200
18292
  function detectRuntimes(cwd) {
18201
18293
  const found = [];
18202
18294
  for (const rt of KNOWN_RUNTIMES) {
18203
- const matchedMarker = rt.markers.find((m) => {
18295
+ const marker = rt.markers.find((m) => {
18204
18296
  try {
18205
18297
  return existsSync17(join23(cwd, m));
18206
18298
  } catch {
18207
18299
  return false;
18208
18300
  }
18209
18301
  });
18302
+ const matchedMarker = marker ?? rt.detectExtra?.(cwd) ?? void 0;
18210
18303
  if (matchedMarker !== void 0) found.push({ ...rt, matchedMarker });
18211
18304
  }
18212
18305
  return found;
@@ -18226,10 +18319,14 @@ function detectionHeadline(detected) {
18226
18319
  return `Detected ${detected.length} agent runtimes: ${parts.join(", ")}.`;
18227
18320
  }
18228
18321
  function neutralOnlyCaveat(detected) {
18229
- if (detected.length === 0 || hasInLoopRuntime(detected)) return null;
18230
- const names = detected.map((r) => r.label).join(", ");
18231
- const tracking = [...new Set(detected.map((r) => r.adapterTracking).filter(Boolean))].join(", ");
18232
- 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}.` : ""}`;
18322
+ const neutral = detected.filter((r) => r.steering === "neutral");
18323
+ if (neutral.length === 0) return null;
18324
+ const names = neutral.map((r) => r.label).join(", ");
18325
+ const tracking = [...new Set(neutral.map((r) => r.adapterTracking).filter(Boolean))].join(", ");
18326
+ const claudeAlso = hasInLoopRuntime(detected);
18327
+ const lead = claudeAlso ? `In-loop steering (deny-before-execute) ships for Claude Code only today: Claude Code here gets it, but on ${names} your live protection is ` : `In-loop steering (deny-before-execute) ships for Claude Code only today, so on ${names} your live protection is `;
18328
+ const tail = claudeAlso ? "" : " The Claude hook wiring is still written and is inert unless Claude Code runs in this repository.";
18329
+ return `${lead}the agent-neutral layers: pre-commit and CI.${tail}${tracking ? ` A native in-loop adapter is tracked in ${tracking}.` : ""}`;
18233
18330
  }
18234
18331
  var KNOWN_RUNTIMES;
18235
18332
  var init_runtimes = __esm({
@@ -18238,8 +18335,13 @@ var init_runtimes = __esm({
18238
18335
  KNOWN_RUNTIMES = [
18239
18336
  {
18240
18337
  id: "claude-code",
18338
+ // Markers are Claude Code files `init` never writes, so detection never mistakes
18339
+ // TamperWard's own generated `.claude/settings.json` for evidence a human runs
18340
+ // Claude here (#526). `detectExtra` looks at that generated file's CONTENT for
18341
+ // real use (configuration beyond the two hooks init merges in).
18241
18342
  label: "Claude Code",
18242
- markers: [".claude/settings.json", ".claude"],
18343
+ markers: ["CLAUDE.md", ".claude/settings.local.json", ".claude/commands", ".claude/agents"],
18344
+ detectExtra: (cwd) => claudeSettingsBeyondTamperward(cwd) ? ".claude/settings.json" : null,
18243
18345
  steering: "in-loop",
18244
18346
  note: "native in-loop steering (PreToolUse deny + Stop sweep) plus the agent-neutral layers."
18245
18347
  },
@@ -18273,7 +18375,7 @@ var init_runtimes = __esm({
18273
18375
 
18274
18376
  // src/cli/onboard.ts
18275
18377
  import { execFileSync as execFileSync12 } from "node:child_process";
18276
- import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync21, realpathSync as realpathSync11, rmSync as rmSync10 } from "node:fs";
18378
+ import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync22, realpathSync as realpathSync11, rmSync as rmSync10 } from "node:fs";
18277
18379
  import { tmpdir as tmpdir7 } from "node:os";
18278
18380
  import { join as join24, relative as relative7, resolve as resolve12, sep as sep5 } from "node:path";
18279
18381
  import { createInterface } from "node:readline";
@@ -18349,7 +18451,7 @@ function writeVerifyCommand(cwd, command) {
18349
18451
  return `${POLICY_FILE} is not a regular file; refusing to follow or replace a symlink or special file`;
18350
18452
  }
18351
18453
  const mode = existingMode(path, 420);
18352
- const original = kind === "file" ? readFileSync21(path, "utf8") : null;
18454
+ const original = kind === "file" ? readFileSync22(path, "utf8") : null;
18353
18455
  let next;
18354
18456
  try {
18355
18457
  const doc = parseDocument(original && original.trim() ? original : "version: 1\n");
@@ -18950,7 +19052,7 @@ var init_capture = __esm({
18950
19052
 
18951
19053
  // src/research/manifest.ts
18952
19054
  import { createHash as createHash13 } from "node:crypto";
18953
- import { readFileSync as readFileSync22 } from "node:fs";
19055
+ import { readFileSync as readFileSync23 } from "node:fs";
18954
19056
  import { dirname as dirname10, resolve as resolve14 } from "node:path";
18955
19057
  function taskFrom(raw, index, manifestDir) {
18956
19058
  const where = `task #${index + 1}`;
@@ -18984,7 +19086,7 @@ function readManifest(path) {
18984
19086
  const abs = resolve14(path);
18985
19087
  let bytes;
18986
19088
  try {
18987
- bytes = readFileSync22(abs);
19089
+ bytes = readFileSync23(abs);
18988
19090
  } catch (e) {
18989
19091
  throw new ResearchError(`cannot read task manifest ${abs}: ${e instanceof Error ? e.message : String(e)}`);
18990
19092
  }
@@ -19196,7 +19298,7 @@ var init_record = __esm({
19196
19298
 
19197
19299
  // src/research/run.ts
19198
19300
  import { execFileSync as execFileSync13 } from "node:child_process";
19199
- import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync23, renameSync as renameSync4, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
19301
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync24, renameSync as renameSync4, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
19200
19302
  import { join as join25, resolve as resolve15 } from "node:path";
19201
19303
  function git6(args, cwd) {
19202
19304
  return execFileSync13("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
@@ -19207,7 +19309,7 @@ function pairRecordPath(ledger, task, pair) {
19207
19309
  function resumableRecord(path, expected) {
19208
19310
  let raw;
19209
19311
  try {
19210
- raw = JSON.parse(readFileSync23(path, "utf8"));
19312
+ raw = JSON.parse(readFileSync24(path, "utf8"));
19211
19313
  } catch (e) {
19212
19314
  throw new ResearchError(`ledger record ${path} is malformed (${errorMessage(e).split("\n")[0]}); remove it to regenerate the pair, or use a new --out`);
19213
19315
  }
@@ -19630,7 +19732,7 @@ var init_run2 = __esm({
19630
19732
  });
19631
19733
 
19632
19734
  // src/research/summarize.ts
19633
- import { readdirSync as readdirSync7, readFileSync as readFileSync24 } from "node:fs";
19735
+ import { readdirSync as readdirSync7, readFileSync as readFileSync25 } from "node:fs";
19634
19736
  import { join as join26, resolve as resolve16 } from "node:path";
19635
19737
  function readLedger2(dir) {
19636
19738
  const pairsDir = join26(resolve16(dir), "pairs");
@@ -19645,7 +19747,7 @@ function readLedger2(dir) {
19645
19747
  const path = join26(pairsDir, n);
19646
19748
  let raw;
19647
19749
  try {
19648
- raw = JSON.parse(readFileSync24(path, "utf8"));
19750
+ raw = JSON.parse(readFileSync25(path, "utf8"));
19649
19751
  } catch (e) {
19650
19752
  throw new ResearchError(`ledger record ${path} is not valid JSON: ${errorMessage(e)}`);
19651
19753
  }
@@ -20352,7 +20454,7 @@ var init_main = __esm({
20352
20454
  // src/cli/index.ts
20353
20455
  init_hook_client();
20354
20456
  init_exit();
20355
- import { readFileSync as readFileSync25 } from "node:fs";
20457
+ import { readFileSync as readFileSync26 } from "node:fs";
20356
20458
  function loadMain() {
20357
20459
  return Promise.resolve().then(() => (init_main(), main_exports));
20358
20460
  }
@@ -20365,7 +20467,7 @@ async function launch(argv) {
20365
20467
  if (kind && hookServiceEnabled()) {
20366
20468
  let raw = null;
20367
20469
  try {
20368
- raw = readFileSync25(0, "utf8");
20470
+ raw = readFileSync26(0, "utf8");
20369
20471
  } catch {
20370
20472
  raw = null;
20371
20473
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.29.11",
3
+ "version": "2.29.13",
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",