tamperward 1.1.0 → 1.2.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 -3
- package/dist/cli/index.js +66 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,14 +92,17 @@ with the same engine it ships.
|
|
|
92
92
|
per-line old/new line numbers correct across multiple hunks.
|
|
93
93
|
- `src/git/build.ts` — the git adapter: range / staged / worktree views, enriching
|
|
94
94
|
`before`/`after` with full file content for the AST detectors.
|
|
95
|
-
- `src/detectors/` — the **
|
|
95
|
+
- `src/detectors/` — the **nine mechanical rules**: `no-verify`, `ts-any-cast`,
|
|
96
96
|
`lint-suppression`, `test-skip`, `coverage-lowering`, `ci-tampering`,
|
|
97
97
|
`hook-tampering`, `test-deletion` (the last counts `it()/test()` via the TS AST,
|
|
98
|
-
and handles delete / rename-out-of-glob / shell mutation)
|
|
98
|
+
and handles delete / rename-out-of-glob / shell mutation), and `snapshot-rewrite`
|
|
99
|
+
(a `warn`: re-recording a snapshot/golden expectation from current output — the one
|
|
100
|
+
rule built from measured demand, after the affordance experiment put the move at a
|
|
101
|
+
70% attempt and 100% through rate; see `harness/PREDICTION-affordance.md`).
|
|
99
102
|
- `src/engine.ts` — runs the enabled rules over `Change[]`; honours `policy.ignore`.
|
|
100
103
|
- `src/cli/` — `tamperward check --staged | --worktree | --diff <base>...<head>`,
|
|
101
104
|
exit 1 on any blocking finding.
|
|
102
|
-
- `test/` —
|
|
105
|
+
- `test/` — 226 tests, including the AST-vs-regex, self-hosting precision, and
|
|
103
106
|
pre-go-live audit regression cases, and the renderer accessibility contract.
|
|
104
107
|
|
|
105
108
|
- `src/adapters/claude/` + `src/cli/hook.ts` — the agent layer: `tamperward hook claude`
|
package/dist/cli/index.js
CHANGED
|
@@ -425,6 +425,10 @@ function defaultPolicy() {
|
|
|
425
425
|
"**/package.json"
|
|
426
426
|
],
|
|
427
427
|
ci: [".github/workflows/**"],
|
|
428
|
+
// Recorded expected outputs. An assertion stored as data is still an assertion;
|
|
429
|
+
// rewriting it from current output is the snapshot-update move the affordance
|
|
430
|
+
// experiment measured at a 70% attempt / 100% through rate (snapshot-rewrite).
|
|
431
|
+
snapshots: ["**/*.snap", "**/__snapshots__/**", "**/golden/**", "**/*.golden.*"],
|
|
428
432
|
hooks: [".husky/**", "**/lefthook.*", ".tamperward.yml", "**/.tamperward.yml"]
|
|
429
433
|
},
|
|
430
434
|
rules: {
|
|
@@ -440,6 +444,9 @@ function defaultPolicy() {
|
|
|
440
444
|
"ci-tampering": { severity: "block" },
|
|
441
445
|
"hook-tampering": { severity: "block" },
|
|
442
446
|
"no-verify": { severity: "block" },
|
|
447
|
+
// mechanical but intent-ambiguous: updating a snapshot is the legitimate workflow
|
|
448
|
+
// when intended output changes. WARN until the §7 evidence path earns block.
|
|
449
|
+
"snapshot-rewrite": { severity: "warn" },
|
|
443
450
|
// heuristic — warn until precision is measured (SPEC §7)
|
|
444
451
|
"assertion-weakening": { severity: "warn" },
|
|
445
452
|
"guard-removal": { severity: "warn" }
|
|
@@ -1223,6 +1230,63 @@ var testDeletion = {
|
|
|
1223
1230
|
}
|
|
1224
1231
|
};
|
|
1225
1232
|
|
|
1233
|
+
// src/detectors/snapshot-rewrite.ts
|
|
1234
|
+
var RULE8 = "snapshot-rewrite";
|
|
1235
|
+
var RUNNER2 = /\b(?:jest|vitest|playwright|ava)\b/;
|
|
1236
|
+
var UPDATE_ANY = /--update-?[sS]napshots?\b/;
|
|
1237
|
+
var UPDATE_WITH_RUNNER = /(?:^|\s)(?:-u|--update)(?:\s|$)/;
|
|
1238
|
+
var REGEN_SCRIPT = /\b(?:update|regen(?:erate)?|re?bless)[-_.]?(?:golden|snapshots?|baselines?|expected)\b/i;
|
|
1239
|
+
var MUTATE = /(?:^|\s)(?:rm|cp|mv|tee|truncate|dd)(?:\s|$)|sed\s+-i|>{1,2}/;
|
|
1240
|
+
function namesProtectedSnapshot(seg, policy) {
|
|
1241
|
+
for (const t of tokens(seg)) {
|
|
1242
|
+
const path = unquote(t).replace(/^>{1,2}/, "");
|
|
1243
|
+
if (isProtected(path, policy, "snapshots")) return path;
|
|
1244
|
+
}
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
var snapshotRewrite = {
|
|
1248
|
+
id: RULE8,
|
|
1249
|
+
surface: ["command", "file"],
|
|
1250
|
+
certainty: "mechanical",
|
|
1251
|
+
run(changes, policy) {
|
|
1252
|
+
const out = [];
|
|
1253
|
+
const warn = (input) => out.push(makeFinding(RULE8, policy, { ...input, defaultSeverity: "warn" }));
|
|
1254
|
+
for (const c of changes) {
|
|
1255
|
+
if (c.kind === "command") {
|
|
1256
|
+
for (const seg of segments(c.raw)) {
|
|
1257
|
+
let why = null;
|
|
1258
|
+
if (UPDATE_ANY.test(seg)) why = "the snapshot-update flag rewrites every failing snapshot to match current output";
|
|
1259
|
+
else if (RUNNER2.test(seg) && UPDATE_WITH_RUNNER.test(seg)) why = "running the test runner in update mode rewrites failing snapshots to match current output";
|
|
1260
|
+
else if (REGEN_SCRIPT.test(seg)) why = "a regeneration script rewrites the recorded expected output from current output";
|
|
1261
|
+
else {
|
|
1262
|
+
const path = MUTATE.test(seg) ? namesProtectedSnapshot(seg, policy) : null;
|
|
1263
|
+
if (path) why = `the command overwrites or removes the protected snapshot ${path}`;
|
|
1264
|
+
}
|
|
1265
|
+
if (why) {
|
|
1266
|
+
warn({
|
|
1267
|
+
message: `Snapshot rewrite: ${why}.`,
|
|
1268
|
+
evidence: seg,
|
|
1269
|
+
remediation: "A snapshot is an assertion. If the test failure is a bug, fix the code; only update the snapshot when the INTENDED output changed, and have a human confirm the new expectation is correct."
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
const wasSnap = c.oldPath != null && isProtected(c.oldPath, policy, "snapshots");
|
|
1276
|
+
const isSnap = isProtected(c.path, policy, "snapshots");
|
|
1277
|
+
if (c.op === "add" || !isSnap && !wasSnap) continue;
|
|
1278
|
+
const what = c.op === "delete" ? "deleted" : c.op === "rename" && !isSnap ? `renamed out of the protected snapshot set (to ${c.path})` : "rewritten";
|
|
1279
|
+
warn({
|
|
1280
|
+
file: c.op === "rename" && !isSnap ? c.oldPath : c.path,
|
|
1281
|
+
message: `A recorded expected output was ${what}.`,
|
|
1282
|
+
evidence: c.op === "rename" ? `${c.oldPath} -> ${c.path}` : c.path,
|
|
1283
|
+
remediation: "If the intended output genuinely changed, a human should confirm the new expectation against the requirement; if the test was failing, fix the code instead of re-recording the expectation from it."
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
return out;
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
|
|
1226
1290
|
// src/detectors/index.ts
|
|
1227
1291
|
var allDetectors = [
|
|
1228
1292
|
noVerify,
|
|
@@ -1232,7 +1296,8 @@ var allDetectors = [
|
|
|
1232
1296
|
coverageLowering,
|
|
1233
1297
|
ciTampering,
|
|
1234
1298
|
hookTampering,
|
|
1235
|
-
testDeletion
|
|
1299
|
+
testDeletion,
|
|
1300
|
+
snapshotRewrite
|
|
1236
1301
|
];
|
|
1237
1302
|
|
|
1238
1303
|
// src/engine.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
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",
|