tamperward 2.24.7 → 2.25.1
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/dist/cli/index.js +309 -72
- package/package.json +5 -5
package/dist/cli/index.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __esm = (fn, res) => function __init() {
|
|
5
|
-
|
|
4
|
+
var __esm = (fn, res, err3) => function __init() {
|
|
5
|
+
if (err3) throw err3[0];
|
|
6
|
+
try {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw err3 = [e], e;
|
|
10
|
+
}
|
|
6
11
|
};
|
|
7
12
|
var __export = (target, all) => {
|
|
8
13
|
for (var name in all)
|
|
@@ -559,6 +564,10 @@ function defaultPolicy(version = 1) {
|
|
|
559
564
|
// through the block-count rule (rows, assertion arguments, setup). Severity
|
|
560
565
|
// backed by the real-commit corpus measurement recorded in the rule's SPEC row.
|
|
561
566
|
"test-content-removal": { severity: "block" },
|
|
567
|
+
// the files that SERVE the specs under the same globs — helpers, setup
|
|
568
|
+
// modules, mocks, JSON fixtures — deleted, moved out or shrunk (#443). They
|
|
569
|
+
// define no test, so a review prompt, not a gate; the spec rules cede them.
|
|
570
|
+
"test-support": { severity: "warn" },
|
|
562
571
|
"test-skip": { severity: "block" },
|
|
563
572
|
"ts-any-cast": { severity: "block" },
|
|
564
573
|
// the unambiguous explicit casts + ts-suppression directives
|
|
@@ -3447,9 +3456,62 @@ function assertedType(node) {
|
|
|
3447
3456
|
while (ts.isParenthesizedTypeNode(t)) t = t.type;
|
|
3448
3457
|
return t;
|
|
3449
3458
|
}
|
|
3450
|
-
function
|
|
3459
|
+
function bareKind(type) {
|
|
3460
|
+
let x = type;
|
|
3461
|
+
while (ts.isParenthesizedTypeNode(x)) x = x.type;
|
|
3462
|
+
switch (x.kind) {
|
|
3463
|
+
case ts.SyntaxKind.AnyKeyword:
|
|
3464
|
+
return "any";
|
|
3465
|
+
case ts.SyntaxKind.NeverKeyword:
|
|
3466
|
+
return "never";
|
|
3467
|
+
case ts.SyntaxKind.UnknownKeyword:
|
|
3468
|
+
return "unknown";
|
|
3469
|
+
case ts.SyntaxKind.ObjectKeyword:
|
|
3470
|
+
return "objectish";
|
|
3471
|
+
}
|
|
3472
|
+
if (ts.isTypeLiteralNode(x) && x.members.length === 0) return "empty";
|
|
3473
|
+
if (ts.isTypeReferenceNode(x) && ts.isIdentifier(x.typeName) && !x.typeArguments) {
|
|
3474
|
+
if (x.typeName.text === "Object") return "objectish";
|
|
3475
|
+
if (x.typeName.text === "const") return "const";
|
|
3476
|
+
}
|
|
3477
|
+
return null;
|
|
3478
|
+
}
|
|
3479
|
+
function kindOfType(type, resolveName) {
|
|
3480
|
+
const bare = bareKind(type);
|
|
3481
|
+
if (bare) return bare;
|
|
3482
|
+
let x = type;
|
|
3483
|
+
while (ts.isParenthesizedTypeNode(x)) x = x.type;
|
|
3484
|
+
if (ts.isTypeReferenceNode(x) && ts.isIdentifier(x.typeName) && !x.typeArguments) return resolveName(x.typeName.text);
|
|
3485
|
+
return null;
|
|
3486
|
+
}
|
|
3487
|
+
function buildAliasMap(sf) {
|
|
3488
|
+
const decls = /* @__PURE__ */ new Map();
|
|
3489
|
+
const collect2 = (node) => {
|
|
3490
|
+
if (ts.isTypeAliasDeclaration(node) && (!node.typeParameters || node.typeParameters.length === 0)) decls.set(node.name.text, node.type);
|
|
3491
|
+
ts.forEachChild(node, collect2);
|
|
3492
|
+
};
|
|
3493
|
+
collect2(sf);
|
|
3494
|
+
const resolve16 = (name, seen) => {
|
|
3495
|
+
if (seen.has(name)) return null;
|
|
3496
|
+
seen.add(name);
|
|
3497
|
+
const t = decls.get(name);
|
|
3498
|
+
return t ? kindOfType(t, (n) => resolve16(n, seen)) : null;
|
|
3499
|
+
};
|
|
3500
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3501
|
+
for (const name of decls.keys()) {
|
|
3502
|
+
const k = resolve16(name, /* @__PURE__ */ new Set());
|
|
3503
|
+
if (k) resolved.set(name, k);
|
|
3504
|
+
}
|
|
3505
|
+
return resolved;
|
|
3506
|
+
}
|
|
3507
|
+
function assertedLaunderKind(node, aliasMap) {
|
|
3508
|
+
return kindOfType(assertedType(node), (n) => aliasMap.get(n) ?? null);
|
|
3509
|
+
}
|
|
3510
|
+
function isDoubleCast(node, aliasMap) {
|
|
3451
3511
|
const inner = unparenthesized(node.expression);
|
|
3452
|
-
|
|
3512
|
+
if (!(ts.isAsExpression(inner) || ts.isTypeAssertionExpression(inner))) return false;
|
|
3513
|
+
const k = assertedLaunderKind(inner, aliasMap ?? EMPTY_ALIASES);
|
|
3514
|
+
return k != null && DOUBLE_CAST_INNER.has(k);
|
|
3453
3515
|
}
|
|
3454
3516
|
function surfaceOf(path, src) {
|
|
3455
3517
|
const sf = parseSource(path, src, scriptKind(path));
|
|
@@ -3458,20 +3520,26 @@ function surfaceOf(path, src) {
|
|
|
3458
3520
|
if (diagnostics.length > 0) return null;
|
|
3459
3521
|
const surface = { type: 0, nonNull: 0, assertions: [] };
|
|
3460
3522
|
const lines = src.split("\n");
|
|
3523
|
+
const aliasMap = buildAliasMap(sf);
|
|
3461
3524
|
const record = (kind, node) => {
|
|
3462
3525
|
const line = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
3463
3526
|
surface.assertions.push({ kind, line, text: node.getText(sf).replace(/\s+/g, " "), lineText: (lines[line - 1] ?? "").trim() });
|
|
3464
3527
|
if (kind === "type") surface.type++;
|
|
3465
3528
|
else surface.nonNull++;
|
|
3466
3529
|
};
|
|
3530
|
+
const launderInner = /* @__PURE__ */ new Set();
|
|
3531
|
+
const collectInners = (node) => {
|
|
3532
|
+
if ((ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) && isDoubleCast(node, aliasMap)) launderInner.add(unparenthesized(node.expression));
|
|
3533
|
+
ts.forEachChild(node, collectInners);
|
|
3534
|
+
};
|
|
3535
|
+
collectInners(sf);
|
|
3467
3536
|
const isRow4OrHonest = (node) => {
|
|
3468
|
-
const
|
|
3469
|
-
|
|
3470
|
-
return ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && type.typeName.text === "const";
|
|
3537
|
+
const k = assertedLaunderKind(node, aliasMap);
|
|
3538
|
+
return k === "any" || k === "never" || k === "unknown" || k === "const";
|
|
3471
3539
|
};
|
|
3472
3540
|
const visit = (node) => {
|
|
3473
3541
|
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
3474
|
-
if (!isDoubleCast(node) && !isRow4OrHonest(node)) record("type", node);
|
|
3542
|
+
if (!isDoubleCast(node, aliasMap) && !isRow4OrHonest(node) && !launderInner.has(node)) record("type", node);
|
|
3475
3543
|
} else if (ts.isNonNullExpression(node)) {
|
|
3476
3544
|
record("non-null", node);
|
|
3477
3545
|
}
|
|
@@ -3506,7 +3574,7 @@ function unmatchedAfter(before, after) {
|
|
|
3506
3574
|
}
|
|
3507
3575
|
return out3;
|
|
3508
3576
|
}
|
|
3509
|
-
var RULE2, DECLARATION, OUT_OF_BUDGET_PATH, GENERATED_HEADER, HEADER_LINES, tsCastGrowth;
|
|
3577
|
+
var RULE2, DECLARATION, OUT_OF_BUDGET_PATH, GENERATED_HEADER, HEADER_LINES, DOUBLE_CAST_INNER, EMPTY_ALIASES, tsCastGrowth;
|
|
3510
3578
|
var init_ts_cast_growth = __esm({
|
|
3511
3579
|
"src/detectors/ts-cast-growth.ts"() {
|
|
3512
3580
|
"use strict";
|
|
@@ -3520,6 +3588,8 @@ var init_ts_cast_growth = __esm({
|
|
|
3520
3588
|
OUT_OF_BUDGET_PATH = /(?:^|\/)(?:node_modules|vendor|third_party|dist|build|generated|__generated__)\/|\.(?:generated|gen)\.[cm]?[jt]sx?$/;
|
|
3521
3589
|
GENERATED_HEADER = /@generated\b|\bAUTO-?GENERATED\b|\bDO NOT EDIT\b/i;
|
|
3522
3590
|
HEADER_LINES = 20;
|
|
3591
|
+
DOUBLE_CAST_INNER = /* @__PURE__ */ new Set(["any", "never", "unknown", "empty", "objectish"]);
|
|
3592
|
+
EMPTY_ALIASES = /* @__PURE__ */ new Map();
|
|
3523
3593
|
tsCastGrowth = {
|
|
3524
3594
|
id: RULE2,
|
|
3525
3595
|
surface: ["file"],
|
|
@@ -3561,13 +3631,51 @@ var init_ts_cast_growth = __esm({
|
|
|
3561
3631
|
});
|
|
3562
3632
|
|
|
3563
3633
|
// src/detectors/ts-any-cast.ts
|
|
3564
|
-
function
|
|
3565
|
-
|
|
3634
|
+
function commentIsLiveDirective(text) {
|
|
3635
|
+
let inner = text;
|
|
3636
|
+
if (inner.startsWith("//")) inner = inner.replace(/^\/+/, "");
|
|
3637
|
+
else inner = inner.replace(/^\/\*+/, "").replace(/\*+\/$/, "");
|
|
3638
|
+
for (const raw of inner.split("\n")) {
|
|
3639
|
+
if (DIRECTIVE_START.test(raw.replace(/^\s*\*+/, "").trim())) return true;
|
|
3640
|
+
}
|
|
3641
|
+
return false;
|
|
3642
|
+
}
|
|
3643
|
+
function countLiveDirectives(src) {
|
|
3644
|
+
let n = 0;
|
|
3645
|
+
try {
|
|
3646
|
+
const scanner = ts.createScanner(
|
|
3647
|
+
ts.ScriptTarget.Latest,
|
|
3648
|
+
/* skipTrivia */
|
|
3649
|
+
false,
|
|
3650
|
+
ts.LanguageVariant.Standard,
|
|
3651
|
+
src
|
|
3652
|
+
);
|
|
3653
|
+
let tok = scanner.scan();
|
|
3654
|
+
while (tok !== ts.SyntaxKind.EndOfFileToken) {
|
|
3655
|
+
if (tok === ts.SyntaxKind.SingleLineCommentTrivia || tok === ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
3656
|
+
if (commentIsLiveDirective(scanner.getTokenText())) n++;
|
|
3657
|
+
}
|
|
3658
|
+
tok = scanner.scan();
|
|
3659
|
+
}
|
|
3660
|
+
} catch {
|
|
3661
|
+
}
|
|
3662
|
+
return n;
|
|
3663
|
+
}
|
|
3664
|
+
function countAny(src, path) {
|
|
3665
|
+
const r = { cast: 0, never: 0, broad: 0, double: 0, suppress: 0, jsdoc: 0 };
|
|
3666
|
+
r.suppress = countLiveDirectives(src);
|
|
3667
|
+
r.jsdoc = JS_FILE.test(path) ? countMatches(src, JSDOC_ANY_CAST) : 0;
|
|
3566
3668
|
try {
|
|
3567
3669
|
const sf = parseSource("f.ts", src);
|
|
3568
3670
|
if (!sf) return r;
|
|
3671
|
+
const aliasMap = buildAliasMap(sf);
|
|
3569
3672
|
const visit = (node) => {
|
|
3570
|
-
if (
|
|
3673
|
+
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
3674
|
+
const k = assertedLaunderKind(node, aliasMap);
|
|
3675
|
+
if (k === "any") r.cast++;
|
|
3676
|
+
else if (k === "never") r.never++;
|
|
3677
|
+
if (isDoubleCast(node, aliasMap)) r.double++;
|
|
3678
|
+
}
|
|
3571
3679
|
if (node.kind === ts.SyntaxKind.AnyKeyword) {
|
|
3572
3680
|
let target = node;
|
|
3573
3681
|
let p = target.parent;
|
|
@@ -3576,8 +3684,7 @@ function countAny(src) {
|
|
|
3576
3684
|
p = p.parent;
|
|
3577
3685
|
}
|
|
3578
3686
|
const isCast = p && ts.isAsExpression(p) && p.type === target || p && ts.isTypeAssertionExpression(p) && p.type === target;
|
|
3579
|
-
if (isCast) r.
|
|
3580
|
-
else r.broad++;
|
|
3687
|
+
if (!isCast) r.broad++;
|
|
3581
3688
|
}
|
|
3582
3689
|
ts.forEachChild(node, visit);
|
|
3583
3690
|
};
|
|
@@ -3586,22 +3693,33 @@ function countAny(src) {
|
|
|
3586
3693
|
}
|
|
3587
3694
|
return r;
|
|
3588
3695
|
}
|
|
3589
|
-
function
|
|
3696
|
+
function lineHasLiveDirective(line) {
|
|
3697
|
+
return countLiveDirectives(line) > 0 || FRAGMENT_DIRECTIVE.test(line);
|
|
3698
|
+
}
|
|
3699
|
+
function lineHasRow4Cast(line, aliasMap) {
|
|
3590
3700
|
const sf = parseSource("line.ts", line);
|
|
3591
3701
|
if (!sf) return false;
|
|
3592
3702
|
let found = false;
|
|
3593
3703
|
const visit = (n) => {
|
|
3594
3704
|
if (found) return;
|
|
3595
|
-
if (
|
|
3596
|
-
|
|
3597
|
-
|
|
3705
|
+
if (ts.isAsExpression(n) || ts.isTypeAssertionExpression(n)) {
|
|
3706
|
+
const k = assertedLaunderKind(n, aliasMap);
|
|
3707
|
+
if (k === "any" || k === "never" || isDoubleCast(n, aliasMap)) {
|
|
3708
|
+
found = true;
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3598
3711
|
}
|
|
3599
3712
|
ts.forEachChild(n, visit);
|
|
3600
3713
|
};
|
|
3601
3714
|
visit(sf);
|
|
3602
3715
|
return found;
|
|
3603
3716
|
}
|
|
3604
|
-
|
|
3717
|
+
function aliasMapForAddedLines(c) {
|
|
3718
|
+
const snippet = addedLines(c).map((l) => l.content).join("\n");
|
|
3719
|
+
const sf = parseSource("lines.ts", snippet);
|
|
3720
|
+
return sf ? buildAliasMap(sf) : /* @__PURE__ */ new Map();
|
|
3721
|
+
}
|
|
3722
|
+
var BLOCK_RULE, WARN_RULE, JSDOC_ANY_CAST, JS_FILE, countMatches, DIRECTIVE_START, NARROW_LINE, BROAD_LINE, FRAGMENT_DIRECTIVE, BLOCK_REMEDIATION, WARN_REMEDIATION, tsAnyCast;
|
|
3605
3723
|
var init_ts_any_cast = __esm({
|
|
3606
3724
|
"src/detectors/ts-any-cast.ts"() {
|
|
3607
3725
|
"use strict";
|
|
@@ -3613,13 +3731,13 @@ var init_ts_any_cast = __esm({
|
|
|
3613
3731
|
init_ts_cast_growth();
|
|
3614
3732
|
BLOCK_RULE = "ts-any-cast";
|
|
3615
3733
|
WARN_RULE = "ts-any-launder";
|
|
3616
|
-
|
|
3617
|
-
SUPPRESS = /@ts-(?:ignore|expect-error|nocheck)\b/g;
|
|
3618
|
-
JSDOC_ANY_CAST = /\/\*\*\s*@type\s*\{\s*any\s*\}\s*\*\/\s*\(/g;
|
|
3734
|
+
JSDOC_ANY_CAST = /\/\*\*\s*@type\s*\{\s*(?:any|\*|\?)\s*\}\s*\*\/\s*\(/g;
|
|
3619
3735
|
JS_FILE = /\.(?:js|jsx|mjs|cjs)$/;
|
|
3620
3736
|
countMatches = (s, re) => (s.match(re) || []).length;
|
|
3621
|
-
|
|
3737
|
+
DIRECTIVE_START = /^@ts-(?:ignore|expect-error|nocheck)\b/;
|
|
3738
|
+
NARROW_LINE = /\bas\s+\(*\s*any\b|<\s*any\s*>/;
|
|
3622
3739
|
BROAD_LINE = /:\s*any\b|<[^<>]*\bany\b[^<>]*>/;
|
|
3740
|
+
FRAGMENT_DIRECTIVE = /^\s*\*?\s*@ts-(?:ignore|expect-error|nocheck)\b/;
|
|
3623
3741
|
BLOCK_REMEDIATION = "Fix the underlying type instead of silencing the checker; do not cast to `any`.";
|
|
3624
3742
|
WARN_REMEDIATION = "Prefer a precise type or `unknown` + a guard over `any` here \u2014 flagged for review.";
|
|
3625
3743
|
tsAnyCast = {
|
|
@@ -3633,15 +3751,17 @@ var init_ts_any_cast = __esm({
|
|
|
3633
3751
|
const inTest = protectedCategory(c.path, policy) === "tests";
|
|
3634
3752
|
if (c.after != null && c.op !== "delete") {
|
|
3635
3753
|
const before = c.before ?? "";
|
|
3636
|
-
const a = countAny(c.after);
|
|
3637
|
-
const b = countAny(before);
|
|
3754
|
+
const a = countAny(c.after, c.path);
|
|
3755
|
+
const b = countAny(before, c.path);
|
|
3638
3756
|
const dCast = a.cast - b.cast;
|
|
3757
|
+
const dNever = a.never - b.never;
|
|
3639
3758
|
const dBroad = a.broad - b.broad;
|
|
3640
|
-
const dDouble =
|
|
3641
|
-
const dSuppr =
|
|
3642
|
-
const dJsdoc =
|
|
3759
|
+
const dDouble = a.double - b.double;
|
|
3760
|
+
const dSuppr = a.suppress - b.suppress;
|
|
3761
|
+
const dJsdoc = a.jsdoc - b.jsdoc;
|
|
3643
3762
|
const blockReasons = [];
|
|
3644
3763
|
if (dCast > 0) blockReasons.push("`as any` cast");
|
|
3764
|
+
if (dNever > 0) blockReasons.push("`as never` cast");
|
|
3645
3765
|
if (dDouble > 0) blockReasons.push("`as unknown as` double cast");
|
|
3646
3766
|
if (dSuppr > 0) blockReasons.push("@ts-ignore/@ts-expect-error/@ts-nocheck suppression");
|
|
3647
3767
|
if (dJsdoc > 0) blockReasons.push("JSDoc `@type {any}` cast");
|
|
@@ -3670,8 +3790,9 @@ var init_ts_any_cast = __esm({
|
|
|
3670
3790
|
continue;
|
|
3671
3791
|
}
|
|
3672
3792
|
const jsdocCast = JS_FILE.test(c.path) ? new RegExp(JSDOC_ANY_CAST.source) : null;
|
|
3793
|
+
const aliasMap = aliasMapForAddedLines(c);
|
|
3673
3794
|
for (const l of addedLines(c)) {
|
|
3674
|
-
if (NARROW_LINE.test(l.content) ||
|
|
3795
|
+
if (NARROW_LINE.test(l.content) || lineHasRow4Cast(l.content, aliasMap) || lineHasLiveDirective(l.content) || jsdocCast && jsdocCast.test(l.content)) {
|
|
3675
3796
|
out3.push(
|
|
3676
3797
|
makeFinding(inTest ? WARN_RULE : BLOCK_RULE, policy, {
|
|
3677
3798
|
file: c.path,
|
|
@@ -4412,6 +4533,7 @@ var init_test_skip = __esm({
|
|
|
4412
4533
|
constructor(source) {
|
|
4413
4534
|
this.source = source;
|
|
4414
4535
|
}
|
|
4536
|
+
source;
|
|
4415
4537
|
binders = /* @__PURE__ */ new Map();
|
|
4416
4538
|
static MAX_DEPTH = 4;
|
|
4417
4539
|
binderFor(path, ctx) {
|
|
@@ -4451,6 +4573,10 @@ var init_test_skip = __esm({
|
|
|
4451
4573
|
this.resolver = resolver;
|
|
4452
4574
|
this.depth = depth;
|
|
4453
4575
|
}
|
|
4576
|
+
ctx;
|
|
4577
|
+
path;
|
|
4578
|
+
resolver;
|
|
4579
|
+
depth;
|
|
4454
4580
|
memo = /* @__PURE__ */ new Map();
|
|
4455
4581
|
resolving = /* @__PURE__ */ new Set();
|
|
4456
4582
|
exports = null;
|
|
@@ -5748,7 +5874,7 @@ var init_gh_expression = __esm({
|
|
|
5748
5874
|
"use strict";
|
|
5749
5875
|
EXPR_TOKEN = "__expr__";
|
|
5750
5876
|
MATRIX_TOKEN = "__matrix__";
|
|
5751
|
-
TRUTHY = Symbol("truthy");
|
|
5877
|
+
TRUTHY = /* @__PURE__ */ Symbol("truthy");
|
|
5752
5878
|
truthy = (v) => v === TRUTHY ? true : typeof v === "string" ? v !== "" : typeof v === "number" ? v !== 0 : v === true;
|
|
5753
5879
|
}
|
|
5754
5880
|
});
|
|
@@ -8104,6 +8230,30 @@ They WILL FAIL in v10.0.0
|
|
|
8104
8230
|
}
|
|
8105
8231
|
});
|
|
8106
8232
|
|
|
8233
|
+
// src/detectors/spec-shape.ts
|
|
8234
|
+
function isSpecShaped(path, before, after) {
|
|
8235
|
+
if (SPEC_SUFFIX.test(path)) return true;
|
|
8236
|
+
if (SUPPORT_DIR.test(path)) return false;
|
|
8237
|
+
if (langOf(path) === null) return false;
|
|
8238
|
+
if (before === null && after === null) return true;
|
|
8239
|
+
for (const src of [before, after]) {
|
|
8240
|
+
if (src === null) continue;
|
|
8241
|
+
const n = countTests(src, path);
|
|
8242
|
+
if (n.min > 0 || n.open) return true;
|
|
8243
|
+
}
|
|
8244
|
+
return false;
|
|
8245
|
+
}
|
|
8246
|
+
var SPEC_SUFFIX, SUPPORT_DIR;
|
|
8247
|
+
var init_spec_shape = __esm({
|
|
8248
|
+
"src/detectors/spec-shape.ts"() {
|
|
8249
|
+
"use strict";
|
|
8250
|
+
init_test_deletion();
|
|
8251
|
+
init_files();
|
|
8252
|
+
SPEC_SUFFIX = /(?:\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|\/)test_[^/]*\.py$|_test\.py$|(?:^|\/)conftest\.py$|_test\.go$|_spec\.rb$|_test\.rb$|Tests?\.java$|Tests?\.kts?$|Test\.php$|Tests?\.cs$|(?:^|\/)tests\/[^/]+\.rs$|(?:^|\/)tests\/[^/]+\/main\.rs$)/;
|
|
8253
|
+
SUPPORT_DIR = /(?:^|\/)(?:fixtures|__fixtures__|__mocks__|__snapshots__)\//;
|
|
8254
|
+
}
|
|
8255
|
+
});
|
|
8256
|
+
|
|
8107
8257
|
// src/detectors/reachability.ts
|
|
8108
8258
|
function constTruth(expr) {
|
|
8109
8259
|
const v = constValue(expr);
|
|
@@ -8629,6 +8779,7 @@ var init_test_deletion = __esm({
|
|
|
8629
8779
|
init_hook_wiring();
|
|
8630
8780
|
init_files();
|
|
8631
8781
|
init_repo();
|
|
8782
|
+
init_spec_shape();
|
|
8632
8783
|
init_suite_config();
|
|
8633
8784
|
init_invocation();
|
|
8634
8785
|
init_narrow();
|
|
@@ -8685,7 +8836,7 @@ var init_test_deletion = __esm({
|
|
|
8685
8836
|
};
|
|
8686
8837
|
for (const c of changes) {
|
|
8687
8838
|
if (c.kind === "file") {
|
|
8688
|
-
const isTest = isSpec(c.path);
|
|
8839
|
+
const isTest = isSpec(c.path) && isSpecShaped(c.path, c.before, c.after);
|
|
8689
8840
|
if (c.op === "delete" && isTest) {
|
|
8690
8841
|
if (c.before != null && isRelocation(significantLines(c.before, c.path), countTestBlocks(c.before, c.path))) continue;
|
|
8691
8842
|
out3.push(
|
|
@@ -8696,7 +8847,7 @@ var init_test_deletion = __esm({
|
|
|
8696
8847
|
remediation: "Fix the code under test, not the suite. Deleting a failing test to pass is the move this blocks."
|
|
8697
8848
|
})
|
|
8698
8849
|
);
|
|
8699
|
-
} else if (c.op === "rename" && c.oldPath && isSpec(c.oldPath) && !
|
|
8850
|
+
} else if (c.op === "rename" && c.oldPath && isSpec(c.oldPath) && isSpecShaped(c.oldPath, c.before, c.after) && !isSpec(c.path)) {
|
|
8700
8851
|
out3.push(
|
|
8701
8852
|
makeFinding(RULE9, policy, {
|
|
8702
8853
|
file: c.path,
|
|
@@ -8730,7 +8881,7 @@ var init_test_deletion = __esm({
|
|
|
8730
8881
|
})
|
|
8731
8882
|
);
|
|
8732
8883
|
}
|
|
8733
|
-
} else if (c.op !== "delete" && c.after != null && !
|
|
8884
|
+
} else if (c.op !== "delete" && c.after != null && !isSpec(c.path) && isProtected(c.path, policy, "config") && runnerOf(c.path, c.after)) {
|
|
8734
8885
|
const rn = runnerOf(c.path, c.after);
|
|
8735
8886
|
const readable = rn !== "pytest" || effectivePytestConfig(c.path, ctx);
|
|
8736
8887
|
for (const n of readable ? suiteNarrowings(c.before, c.after, c.path, runnerSamples(c.path, policy, ctx, rn)) : []) {
|
|
@@ -8791,6 +8942,7 @@ var init_test_deletion = __esm({
|
|
|
8791
8942
|
const resolved = root !== null && /(?:^|\/)\.{1,2}(?:\/|$)/.test(t) ? joinRoot(root, t) : null;
|
|
8792
8943
|
const probe = resolved ?? t;
|
|
8793
8944
|
if (!isSpec(probe)) return false;
|
|
8945
|
+
if (!isSpecShaped(probe, root === null ? null : trackedContent(resolved ?? inRepo(t), ctx), null)) return false;
|
|
8794
8946
|
if (!listing || root === null) return true;
|
|
8795
8947
|
const looksLikeFile = /\.[A-Za-z0-9]+$/.test(probe.split("/").pop() ?? "");
|
|
8796
8948
|
if (looksLikeFile || listing.includes(resolved ?? inRepo(t))) return true;
|
|
@@ -8954,6 +9106,7 @@ var init_test_content_removal = __esm({
|
|
|
8954
9106
|
init_finding();
|
|
8955
9107
|
init_test_deletion();
|
|
8956
9108
|
init_files();
|
|
9109
|
+
init_spec_shape();
|
|
8957
9110
|
init_reachability();
|
|
8958
9111
|
RULE10 = "test-content-removal";
|
|
8959
9112
|
MIN_REMOVED_LINES = 3;
|
|
@@ -8981,9 +9134,9 @@ var init_test_content_removal = __esm({
|
|
|
8981
9134
|
}
|
|
8982
9135
|
}
|
|
8983
9136
|
/** Text added outside any spec (a case table moved to `test/fixtures/`): code. */
|
|
8984
|
-
addCode(
|
|
8985
|
-
this.text +=
|
|
8986
|
-
for (let i = 0; i <
|
|
9137
|
+
addCode(ws3) {
|
|
9138
|
+
this.text += ws3;
|
|
9139
|
+
for (let i = 0; i < ws3.length; i++) this.inLiteral.push(0);
|
|
8987
9140
|
}
|
|
8988
9141
|
keeps(wsLine) {
|
|
8989
9142
|
if (wsLine.length === 0) return true;
|
|
@@ -9019,6 +9172,7 @@ var init_test_content_removal = __esm({
|
|
|
9019
9172
|
if (c.kind !== "file" || c.op !== "modify" && c.op !== "rename" || c.before == null || c.after == null) continue;
|
|
9020
9173
|
if (!isProtected(c.path, policy, "tests")) continue;
|
|
9021
9174
|
if (isProtected(c.path, policy, "snapshots")) continue;
|
|
9175
|
+
if (!isSpecShaped(c.path, c.before, c.after)) continue;
|
|
9022
9176
|
const blocksBefore = countTests(c.before, c.path);
|
|
9023
9177
|
const blocksAfter = countTests(c.after, c.path);
|
|
9024
9178
|
if (blocksAfter.min < blocksBefore.min && !blocksAfter.open) continue;
|
|
@@ -9412,6 +9566,87 @@ var init_assertion_weakening = __esm({
|
|
|
9412
9566
|
}
|
|
9413
9567
|
});
|
|
9414
9568
|
|
|
9569
|
+
// src/detectors/test-support.ts
|
|
9570
|
+
function significant(src, path) {
|
|
9571
|
+
const lang = langOf(path);
|
|
9572
|
+
const out3 = [];
|
|
9573
|
+
for (const raw of src.split("\n")) {
|
|
9574
|
+
const l = raw.trim();
|
|
9575
|
+
if (isSignificantLine(l, lang)) out3.push(l);
|
|
9576
|
+
}
|
|
9577
|
+
return out3;
|
|
9578
|
+
}
|
|
9579
|
+
var RULE14, MIN_REMOVED_LINES2, ws2, testSupport;
|
|
9580
|
+
var init_test_support = __esm({
|
|
9581
|
+
"src/detectors/test-support.ts"() {
|
|
9582
|
+
"use strict";
|
|
9583
|
+
init_policy();
|
|
9584
|
+
init_finding();
|
|
9585
|
+
init_spec_shape();
|
|
9586
|
+
init_files();
|
|
9587
|
+
RULE14 = "test-support";
|
|
9588
|
+
MIN_REMOVED_LINES2 = 3;
|
|
9589
|
+
ws2 = (s) => s.replace(/\s+/g, "");
|
|
9590
|
+
testSupport = {
|
|
9591
|
+
id: RULE14,
|
|
9592
|
+
surface: ["file"],
|
|
9593
|
+
certainty: "mechanical",
|
|
9594
|
+
run(changes, policy) {
|
|
9595
|
+
const out3 = [];
|
|
9596
|
+
const isSupport = (p, before, after) => isProtected(p, policy, "tests") && !isProtected(p, policy, "snapshots") && !isSpecShaped(p, before, after);
|
|
9597
|
+
let kept = "";
|
|
9598
|
+
for (const c of changes) {
|
|
9599
|
+
if (c.kind === "file" && c.after != null) kept += ws2(c.after);
|
|
9600
|
+
}
|
|
9601
|
+
for (const c of changes) {
|
|
9602
|
+
if (c.kind !== "file") continue;
|
|
9603
|
+
if (c.op === "delete") {
|
|
9604
|
+
if (!isSupport(c.path, c.before, null)) continue;
|
|
9605
|
+
out3.push(
|
|
9606
|
+
makeFinding(RULE14, policy, {
|
|
9607
|
+
file: c.path,
|
|
9608
|
+
message: "A test-support file was deleted (a helper, setup module or fixture under the test globs; it defines no test).",
|
|
9609
|
+
evidence: c.path,
|
|
9610
|
+
remediation: "Check that nothing in the suite still needs it. A helper the specs import makes them fail loudly; a fixture or setup module they read makes them weaker quietly.",
|
|
9611
|
+
defaultSeverity: "warn"
|
|
9612
|
+
})
|
|
9613
|
+
);
|
|
9614
|
+
} else if (c.op === "rename" && c.oldPath && isSupport(c.oldPath, c.before, c.after) && !isProtected(c.path, policy, "tests")) {
|
|
9615
|
+
out3.push(
|
|
9616
|
+
makeFinding(RULE14, policy, {
|
|
9617
|
+
file: c.path,
|
|
9618
|
+
message: `A test-support file was renamed out of the test globs (${c.oldPath} \u2192 ${c.path}).`,
|
|
9619
|
+
evidence: `${c.oldPath} \u2192 ${c.path}`,
|
|
9620
|
+
remediation: "Keep test helpers and fixtures under the test tree, where the policy protects them.",
|
|
9621
|
+
defaultSeverity: "warn"
|
|
9622
|
+
})
|
|
9623
|
+
);
|
|
9624
|
+
} else if ((c.op === "modify" || c.op === "rename") && c.before != null && c.after != null) {
|
|
9625
|
+
if (!isSupport(c.path, c.before, c.after)) continue;
|
|
9626
|
+
const beforeSig = significant(c.before, c.path);
|
|
9627
|
+
const afterSig = significant(c.after, c.path);
|
|
9628
|
+
const netDrop = beforeSig.length - afterSig.length;
|
|
9629
|
+
if (netDrop < MIN_REMOVED_LINES2) continue;
|
|
9630
|
+
const afterSet = new Set(afterSig);
|
|
9631
|
+
const gone = [...new Set(beforeSig)].filter((l) => !afterSet.has(l) && !kept.includes(ws2(l)));
|
|
9632
|
+
if (gone.length < MIN_REMOVED_LINES2) continue;
|
|
9633
|
+
out3.push(
|
|
9634
|
+
makeFinding(RULE14, policy, {
|
|
9635
|
+
file: c.path,
|
|
9636
|
+
message: `Test-support content removed: ${gone.length} significant line(s) gone from a helper, setup module or fixture under the test globs.`,
|
|
9637
|
+
evidence: gone.slice(0, 3).map((l) => l.slice(0, 80)).join(" | "),
|
|
9638
|
+
remediation: "Check what the specs lose. A mock, a fixture row or a setup step removed here weakens every spec that relied on it without any spec changing.",
|
|
9639
|
+
defaultSeverity: "warn"
|
|
9640
|
+
})
|
|
9641
|
+
);
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
return out3;
|
|
9645
|
+
}
|
|
9646
|
+
};
|
|
9647
|
+
}
|
|
9648
|
+
});
|
|
9649
|
+
|
|
9415
9650
|
// src/detectors/index.ts
|
|
9416
9651
|
var allDetectors;
|
|
9417
9652
|
var init_detectors = __esm({
|
|
@@ -9431,6 +9666,7 @@ var init_detectors = __esm({
|
|
|
9431
9666
|
init_snapshot_rewrite();
|
|
9432
9667
|
init_snapshot_only();
|
|
9433
9668
|
init_assertion_weakening();
|
|
9669
|
+
init_test_support();
|
|
9434
9670
|
allDetectors = [
|
|
9435
9671
|
noVerify,
|
|
9436
9672
|
tsAnyCast,
|
|
@@ -9443,6 +9679,7 @@ var init_detectors = __esm({
|
|
|
9443
9679
|
hookTampering,
|
|
9444
9680
|
testDeletion,
|
|
9445
9681
|
testContentRemoval,
|
|
9682
|
+
testSupport,
|
|
9446
9683
|
snapshotRewrite,
|
|
9447
9684
|
snapshotOnlyRewrite,
|
|
9448
9685
|
assertionWeakening
|
|
@@ -10910,7 +11147,7 @@ function drainEvents(log, offset, maxTotalBytes = MAX_EVENT_SWEEP_BYTES, io = DE
|
|
|
10910
11147
|
};
|
|
10911
11148
|
}
|
|
10912
11149
|
function transientFindings(events, persistentPaths, policy, finalHash) {
|
|
10913
|
-
if (!isEnabled(
|
|
11150
|
+
if (!isEnabled(RULE15, policy)) return [];
|
|
10914
11151
|
const byPath = /* @__PURE__ */ new Map();
|
|
10915
11152
|
for (const e of events) {
|
|
10916
11153
|
if (!isProtected(e.path, policy)) continue;
|
|
@@ -10930,7 +11167,7 @@ function transientFindings(events, persistentPaths, policy, finalHash) {
|
|
|
10930
11167
|
const modeChurn = modes.size > 1;
|
|
10931
11168
|
if (!contentChurn && !modeChurn) continue;
|
|
10932
11169
|
const what = contentChurn ? `content was modified and restored within the turn (${hashes.size} distinct states observed)` : `file mode changed during the turn (${[...modes].map((m) => (m & 4095).toString(8)).join(" \u2192 ")})`;
|
|
10933
|
-
const f = makeFinding(
|
|
11170
|
+
const f = makeFinding(RULE15, policy, {
|
|
10934
11171
|
file: path,
|
|
10935
11172
|
message: `Transient protected mutation: ${what}, leaving no net diff.`,
|
|
10936
11173
|
evidence: evs.slice(0, 4).map((e) => `${e.ts} ${e.hash ?? "gone"}${e.mode != null ? `/${(e.mode & 4095).toString(8)}` : ""}`).join(" | "),
|
|
@@ -10945,14 +11182,14 @@ function transientFindings(events, persistentPaths, policy, finalHash) {
|
|
|
10945
11182
|
}
|
|
10946
11183
|
return out3;
|
|
10947
11184
|
}
|
|
10948
|
-
var
|
|
11185
|
+
var RULE15, MAX_EVENT_READ_BYTES, MAX_EVENT_SWEEP_BYTES, DEFAULT_EVENT_READ_IO;
|
|
10949
11186
|
var init_fs_events = __esm({
|
|
10950
11187
|
"src/detectors/fs-events.ts"() {
|
|
10951
11188
|
"use strict";
|
|
10952
11189
|
init_policy();
|
|
10953
11190
|
init_finding();
|
|
10954
11191
|
init_narrow();
|
|
10955
|
-
|
|
11192
|
+
RULE15 = "transient-protected-mutation";
|
|
10956
11193
|
MAX_EVENT_READ_BYTES = 4 * 1024 * 1024;
|
|
10957
11194
|
MAX_EVENT_SWEEP_BYTES = 16 * 1024 * 1024;
|
|
10958
11195
|
DEFAULT_EVENT_READ_IO = {
|
|
@@ -17799,22 +18036,22 @@ function writeRecordAtomically(path, text) {
|
|
|
17799
18036
|
renameSync4(tmp, path);
|
|
17800
18037
|
}
|
|
17801
18038
|
function freshWorkspace(ledger, task, pair, arm, sourceBase) {
|
|
17802
|
-
const
|
|
17803
|
-
rmSync11(
|
|
18039
|
+
const ws3 = join23(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
|
|
18040
|
+
rmSync11(ws3, { recursive: true, force: true });
|
|
17804
18041
|
mkdirSync8(join23(ledger, "workspaces"), { recursive: true });
|
|
17805
18042
|
try {
|
|
17806
|
-
execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo,
|
|
18043
|
+
execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo, ws3], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
17807
18044
|
} catch (e) {
|
|
17808
18045
|
throw new ResearchError(`task "${task.id}": cannot clone ${task.repo}: ${errorMessage(e).split("\n")[0]}`);
|
|
17809
18046
|
}
|
|
17810
|
-
git6(["config", "user.name", "tamperward-research"],
|
|
17811
|
-
git6(["config", "user.email", "research@tamperward.invalid"],
|
|
18047
|
+
git6(["config", "user.name", "tamperward-research"], ws3);
|
|
18048
|
+
git6(["config", "user.email", "research@tamperward.invalid"], ws3);
|
|
17812
18049
|
const requestedBase = sourceBase ?? task.base;
|
|
17813
18050
|
const candidates = sourceBase !== void 0 ? [sourceBase] : task.base === "HEAD" ? ["HEAD"] : [task.base, `origin/${task.base}`];
|
|
17814
18051
|
for (const rev2 of candidates) {
|
|
17815
18052
|
try {
|
|
17816
|
-
git6(["checkout", "-q", "--detach", rev2],
|
|
17817
|
-
return
|
|
18053
|
+
git6(["checkout", "-q", "--detach", rev2], ws3);
|
|
18054
|
+
return ws3;
|
|
17818
18055
|
} catch {
|
|
17819
18056
|
}
|
|
17820
18057
|
}
|
|
@@ -17837,13 +18074,13 @@ function parseDocument2(text) {
|
|
|
17837
18074
|
return null;
|
|
17838
18075
|
}
|
|
17839
18076
|
}
|
|
17840
|
-
function runGated(
|
|
18077
|
+
function runGated(ws3, base, task, argv, env, agentBudget) {
|
|
17841
18078
|
const observed = {};
|
|
17842
18079
|
const { result: code2, out: captured } = withEnv2(
|
|
17843
18080
|
env,
|
|
17844
18081
|
() => captureStdout(
|
|
17845
18082
|
() => runEnvelope({
|
|
17846
|
-
cwd:
|
|
18083
|
+
cwd: ws3,
|
|
17847
18084
|
base,
|
|
17848
18085
|
cmd: task.verify.command,
|
|
17849
18086
|
budget: task.verify.budget,
|
|
@@ -17884,8 +18121,8 @@ function runGated(ws2, base, task, argv, env, agentBudget) {
|
|
|
17884
18121
|
}
|
|
17885
18122
|
};
|
|
17886
18123
|
}
|
|
17887
|
-
function runUngated(
|
|
17888
|
-
const r = withEnv2(env, () => runAgentSupervised(argv,
|
|
18124
|
+
function runUngated(ws3, argv, env, agentBudget) {
|
|
18125
|
+
const r = withEnv2(env, () => runAgentSupervised(argv, ws3, agentBudget, void 0, void 0, true));
|
|
17889
18126
|
return {
|
|
17890
18127
|
exit_code: r.failure ? null : r.exit,
|
|
17891
18128
|
signal: r.signal ?? null,
|
|
@@ -17910,20 +18147,20 @@ function unobservedOutcome() {
|
|
|
17910
18147
|
honest_completion: false
|
|
17911
18148
|
};
|
|
17912
18149
|
}
|
|
17913
|
-
function trustedPolicyAt(base,
|
|
18150
|
+
function trustedPolicyAt(base, ws3) {
|
|
17914
18151
|
try {
|
|
17915
|
-
return { policy: loadPolicyAt(base,
|
|
18152
|
+
return { policy: loadPolicyAt(base, ws3) ?? defaultPolicy() };
|
|
17916
18153
|
} catch (e) {
|
|
17917
18154
|
return { failure: `trusted policy at ${base.slice(0, 10)} could not be loaded: ${errorMessage(e).split("\n")[0]}` };
|
|
17918
18155
|
}
|
|
17919
18156
|
}
|
|
17920
|
-
function observeOutcome(
|
|
17921
|
-
const trusted = trustedPolicyAt(base,
|
|
18157
|
+
function observeOutcome(ws3, base, head, task) {
|
|
18158
|
+
const trusted = trustedPolicyAt(base, ws3);
|
|
17922
18159
|
if ("failure" in trusted) return { outcome: unobservedOutcome(), unmeasurable: trusted.failure };
|
|
17923
18160
|
const policy = trusted.policy;
|
|
17924
18161
|
const verify = parseDocument2(
|
|
17925
18162
|
captureStdout(
|
|
17926
|
-
() => runVerify({ cwd:
|
|
18163
|
+
() => runVerify({ cwd: ws3, base, cmd: task.verify.command, budget: task.verify.budget, json: true, policyOverride: policy })
|
|
17927
18164
|
).out
|
|
17928
18165
|
);
|
|
17929
18166
|
const stageExit = (stage) => isRecord(stage) ? finiteNumber(stage.exit) ?? null : null;
|
|
@@ -17943,11 +18180,11 @@ function observeOutcome(ws2, base, head, task) {
|
|
|
17943
18180
|
}
|
|
17944
18181
|
};
|
|
17945
18182
|
collect2(parseDocument2(captureStdout(
|
|
17946
|
-
() => runCheck({ cwd:
|
|
18183
|
+
() => runCheck({ cwd: ws3, worktree: true, json: true, policyOverride: policy, includeUntracked: true, ciLayer: true })
|
|
17947
18184
|
).out));
|
|
17948
18185
|
if (head !== base) {
|
|
17949
18186
|
collect2(parseDocument2(captureStdout(
|
|
17950
|
-
() => runCheck({ cwd:
|
|
18187
|
+
() => runCheck({ cwd: ws3, diff: `${base}...${head}`, json: true, policyOverride: policy })
|
|
17951
18188
|
).out));
|
|
17952
18189
|
}
|
|
17953
18190
|
const expectedCheckDocuments = head !== base ? 2 : 1;
|
|
@@ -17970,39 +18207,39 @@ function observeOutcome(ws2, base, head, task) {
|
|
|
17970
18207
|
return { outcome, unmeasurable };
|
|
17971
18208
|
}
|
|
17972
18209
|
function runTrajectory(ledger, task, pair, arm, adapter, opts, sourceBase) {
|
|
17973
|
-
const
|
|
17974
|
-
const prep = { id: task.id, prompt: task.prompt, cwd:
|
|
18210
|
+
const ws3 = freshWorkspace(ledger, task, pair, arm, sourceBase);
|
|
18211
|
+
const prep = { id: task.id, prompt: task.prompt, cwd: ws3, base: git6(["rev-parse", "HEAD"], ws3), arm, model: opts.model };
|
|
17975
18212
|
if (arm === "gated" && adapter.prepareGated) {
|
|
17976
18213
|
captureStdout(() => adapter.prepareGated?.(prep));
|
|
17977
|
-
if (git6(["status", "--porcelain"],
|
|
17978
|
-
git6(["add", "-A"],
|
|
17979
|
-
git6(["commit", "-qm", "tamperward research: wire enforcement (gated arm)"],
|
|
18214
|
+
if (git6(["status", "--porcelain"], ws3)) {
|
|
18215
|
+
git6(["add", "-A"], ws3);
|
|
18216
|
+
git6(["commit", "-qm", "tamperward research: wire enforcement (gated arm)"], ws3);
|
|
17980
18217
|
}
|
|
17981
18218
|
}
|
|
17982
|
-
const base = git6(["rev-parse", "HEAD"],
|
|
18219
|
+
const base = git6(["rev-parse", "HEAD"], ws3);
|
|
17983
18220
|
const launch2 = adapter.launch({ ...prep, base });
|
|
17984
18221
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17985
18222
|
let agent;
|
|
17986
18223
|
let treatment = null;
|
|
17987
18224
|
if (arm === "gated") {
|
|
17988
|
-
const gated = runGated(
|
|
18225
|
+
const gated = runGated(ws3, base, task, launch2.argv, launch2.env, opts.agentBudget);
|
|
17989
18226
|
agent = gated.agent;
|
|
17990
18227
|
treatment = gated.treatment;
|
|
17991
18228
|
} else {
|
|
17992
|
-
agent = runUngated(
|
|
18229
|
+
agent = runUngated(ws3, launch2.argv, launch2.env, opts.agentBudget);
|
|
17993
18230
|
}
|
|
17994
18231
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17995
|
-
const head = git6(["rev-parse", "HEAD"],
|
|
18232
|
+
const head = git6(["rev-parse", "HEAD"], ws3);
|
|
17996
18233
|
const protectedOnly = (rel) => {
|
|
17997
18234
|
try {
|
|
17998
|
-
return isProtected(rel, loadPolicyAt(base,
|
|
18235
|
+
return isProtected(rel, loadPolicyAt(base, ws3) ?? defaultPolicy());
|
|
17999
18236
|
} catch {
|
|
18000
18237
|
return true;
|
|
18001
18238
|
}
|
|
18002
18239
|
};
|
|
18003
|
-
const fingerprintBefore = treeFingerprint(
|
|
18004
|
-
const observed = observeOutcome(
|
|
18005
|
-
const movedDuringObservation = treeFingerprint(
|
|
18240
|
+
const fingerprintBefore = treeFingerprint(ws3, protectedOnly);
|
|
18241
|
+
const observed = observeOutcome(ws3, base, head, task);
|
|
18242
|
+
const movedDuringObservation = treeFingerprint(ws3, protectedOnly) !== fingerprintBefore;
|
|
18006
18243
|
const unmeasurable = agent.unmeasurable ?? observed.unmeasurable ?? (movedDuringObservation ? "NOT_QUIESCENT: the workspace changed while its outcome was being observed" : null);
|
|
18007
18244
|
const { unmeasurable: _agentUnmeasurable, ...agentRecord } = agent;
|
|
18008
18245
|
const outcome = unmeasurable === null ? observed.outcome : unobservedOutcome();
|
|
@@ -18010,7 +18247,7 @@ function runTrajectory(ledger, task, pair, arm, adapter, opts, sourceBase) {
|
|
|
18010
18247
|
const releasedGreen = outcome.visible_green && agentSucceeded && (treatment === null || treatment.disposition === "passed" && treatment.exit_code === 0);
|
|
18011
18248
|
return {
|
|
18012
18249
|
arm,
|
|
18013
|
-
workspace:
|
|
18250
|
+
workspace: ws3,
|
|
18014
18251
|
base,
|
|
18015
18252
|
head,
|
|
18016
18253
|
started_at: startedAt,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.25.1",
|
|
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",
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/node": "^22.20.1",
|
|
59
|
-
"@types/picomatch": "^
|
|
60
|
-
"ajv": "^8.
|
|
61
|
-
"esbuild": "^0.
|
|
59
|
+
"@types/picomatch": "^4.0.3",
|
|
60
|
+
"ajv": "^8.20.0",
|
|
61
|
+
"esbuild": "^0.28.2",
|
|
62
62
|
"semver": "7.8.5",
|
|
63
63
|
"vitepress": "^2.0.0-alpha.19",
|
|
64
|
-
"vitest": "^
|
|
64
|
+
"vitest": "^5.0.0"
|
|
65
65
|
}
|
|
66
66
|
}
|