agent-roast 0.23.0 → 0.24.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 +1 -1
- package/dist/{chunk-L4XLCXC2.js → chunk-U5MFYERX.js} +47 -4
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ Measured, with the denominators and the caveats in [docs/benchmarks.md](docs/ben
|
|
|
112
112
|
deliberate, and not specific to AI. agent-roast is a tripwire that asks about them. On repositories it had not seen, between a quarter and
|
|
113
113
|
two thirds of its blocks were questions with a harmless answer, depending on the sample.**
|
|
114
114
|
|
|
115
|
-
- **
|
|
115
|
+
- **165 of 165** hand-labeled cheating edits are caught and **154 of 154** harmless look-alikes (reformats, moves, raised
|
|
116
116
|
thresholds, environment-gated skips) stay silent. The cases were written with the detector, so they guard against regressions;
|
|
117
117
|
they do not prove accuracy. One gap is documented and still open.
|
|
118
118
|
- **A catalog of cheats that someone else wrote.** Of the nine hacks in [reward-hack-zoo](https://github.com/MatthewWaller/reward-hack-zoo) (hand-written specimens; none from a model) four are reported (a skip, a patched target, a `conftest.py` that rewrites reports,
|
|
@@ -4538,6 +4538,32 @@ function gatingCalls(file, text) {
|
|
|
4538
4538
|
if (!(/\.gitlab-ci/.test(file) || CI_GATE.test(gatingTriggers(text) ?? text))) return [];
|
|
4539
4539
|
return [...text.matchAll(/^\s*-?\s*uses:\s*["']?\.\/(\.github\/workflows\/[^\s"'#@]+\.ya?ml)/gm)].map((match) => match[1]);
|
|
4540
4540
|
}
|
|
4541
|
+
function lostCalls(file, oldText, newText) {
|
|
4542
|
+
if (!CI_FILE.test(file)) return [];
|
|
4543
|
+
const before = gatingCalls(file, oldText ?? "");
|
|
4544
|
+
if (!before.length) return [];
|
|
4545
|
+
const after = new Set(gatingCalls(file, newText ?? ""));
|
|
4546
|
+
return before.filter((workflow) => !after.has(workflow));
|
|
4547
|
+
}
|
|
4548
|
+
function calledTestCommands(text) {
|
|
4549
|
+
const directories = [...new Set([...text.matchAll(/^\s*(?:-\s+)?working-directory\s*:\s*["']?([^\s"'#]+)/gm)].map((match) => match[1]))].sort().join(",");
|
|
4550
|
+
return ciTestEntries(text).map((entry) => `${entry.command.replace(/\s#.*$/, "").replace(/^\s*-?\s*(?:run\s*:\s*)?/, "").replace(/\s+/g, " ").trim()} @${directories}`);
|
|
4551
|
+
}
|
|
4552
|
+
function resolveLostCallers(lost, commit) {
|
|
4553
|
+
const out = [];
|
|
4554
|
+
for (const [file, { content, stillCalled }] of lost) {
|
|
4555
|
+
if (stillCalled || gatingTestCommands(file, content).length > 0 || calledTestCommands(content).length === 0) continue;
|
|
4556
|
+
out.push(finding(
|
|
4557
|
+
"guard-weakened",
|
|
4558
|
+
file,
|
|
4559
|
+
1,
|
|
4560
|
+
"nothing calls this workflow on a push or pull request any more",
|
|
4561
|
+
"Workflow keeps its tests, but the workflow that called it is gone",
|
|
4562
|
+
commit
|
|
4563
|
+
));
|
|
4564
|
+
}
|
|
4565
|
+
return out;
|
|
4566
|
+
}
|
|
4541
4567
|
function resolveCiTestSteps(counts, commit, elsewhere = /* @__PURE__ */ new Set(), called = /* @__PURE__ */ new Set()) {
|
|
4542
4568
|
const before = counts.reduce((sum, item) => sum + item.before, 0);
|
|
4543
4569
|
const after = counts.reduce((sum, item) => sum + item.after + item.reusableAfter, 0);
|
|
@@ -4800,6 +4826,8 @@ async function compareSnapshot(options, cwd, snapshot) {
|
|
|
4800
4826
|
const guardFindings = [];
|
|
4801
4827
|
const ciCounts = [];
|
|
4802
4828
|
const calledInChange = /* @__PURE__ */ new Set();
|
|
4829
|
+
const lostCalledWorkflows = /* @__PURE__ */ new Set();
|
|
4830
|
+
const touchedCiContent = /* @__PURE__ */ new Map();
|
|
4803
4831
|
let productionChanged = false;
|
|
4804
4832
|
const decode = (content) => {
|
|
4805
4833
|
totalBytes += content.length;
|
|
@@ -4837,8 +4865,12 @@ async function compareSnapshot(options, cwd, snapshot) {
|
|
|
4837
4865
|
const newText = await readNew(change);
|
|
4838
4866
|
guardFindings.push(...analyzeGuardFile(change.newPath, oldText, newText, refs.headSha));
|
|
4839
4867
|
const ci = ciTestSteps(change.newPath, oldText, newText);
|
|
4840
|
-
if (ci)
|
|
4841
|
-
|
|
4868
|
+
if (ci) {
|
|
4869
|
+
ciCounts.push(ci);
|
|
4870
|
+
touchedCiContent.set(change.newPath, newText);
|
|
4871
|
+
for (const workflow of lostCalls(change.newPath, oldText, newText)) lostCalledWorkflows.add(workflow);
|
|
4872
|
+
if (newText !== null) for (const workflow of gatingCalls(change.newPath, newText)) calledInChange.add(workflow);
|
|
4873
|
+
}
|
|
4842
4874
|
result.filesInspected++;
|
|
4843
4875
|
} catch (error) {
|
|
4844
4876
|
assertNotAborted(options.signal);
|
|
@@ -4897,7 +4929,8 @@ async function compareSnapshot(options, cwd, snapshot) {
|
|
|
4897
4929
|
result.complete = result.skippedFiles.length === 0;
|
|
4898
4930
|
const rules = policy.config.rules ?? {};
|
|
4899
4931
|
const elsewhere = /* @__PURE__ */ new Set(), called = new Set(calledInChange);
|
|
4900
|
-
|
|
4932
|
+
const untouchedLostCallerContent = /* @__PURE__ */ new Map();
|
|
4933
|
+
if (ciCounts.some((item) => item.before > 0 && item.after === 0 && item.reusableAfter === 0) || lostCalledWorkflows.size > 0) {
|
|
4901
4934
|
const touched = new Set(changes.flatMap((change) => [change.oldPath, change.newPath]));
|
|
4902
4935
|
const tree = /^[a-f0-9]{40,64}$/.test(refs.headSha) ? refs.headSha : refs.baseSha;
|
|
4903
4936
|
try {
|
|
@@ -4906,6 +4939,7 @@ async function compareSnapshot(options, cwd, snapshot) {
|
|
|
4906
4939
|
const text = new TextDecoder().decode(await readBlob(cwd, file.oid, options.signal, 512 * 1024));
|
|
4907
4940
|
for (const command of enforcedTestCommands(file.path, text)) elsewhere.add(command);
|
|
4908
4941
|
for (const workflow of gatingCalls(file.path, text)) called.add(workflow);
|
|
4942
|
+
if (lostCalledWorkflows.has(file.path)) untouchedLostCallerContent.set(file.path, text);
|
|
4909
4943
|
} catch (error) {
|
|
4910
4944
|
assertNotAborted(options.signal);
|
|
4911
4945
|
}
|
|
@@ -4914,7 +4948,16 @@ async function compareSnapshot(options, cwd, snapshot) {
|
|
|
4914
4948
|
assertNotAborted(options.signal);
|
|
4915
4949
|
}
|
|
4916
4950
|
}
|
|
4917
|
-
|
|
4951
|
+
const ciFindings = resolveCiTestSteps(ciCounts, refs.headSha, elsewhere, called);
|
|
4952
|
+
const alreadyFlagged = new Set(ciFindings.map((found) => found.file));
|
|
4953
|
+
const lostCallers = /* @__PURE__ */ new Map();
|
|
4954
|
+
for (const workflow of lostCalledWorkflows) {
|
|
4955
|
+
if (alreadyFlagged.has(workflow)) continue;
|
|
4956
|
+
const content = touchedCiContent.has(workflow) ? touchedCiContent.get(workflow) ?? null : untouchedLostCallerContent.get(workflow) ?? null;
|
|
4957
|
+
if (content === null) continue;
|
|
4958
|
+
lostCallers.set(workflow, { content, stillCalled: called.has(workflow) });
|
|
4959
|
+
}
|
|
4960
|
+
result.infractions.push(...[...resolveTampering(tamperingFiles, productionChanged), ...guardFindings, ...ciFindings, ...resolveLostCallers(lostCallers, refs.headSha)].filter((finding2) => rules[finding2.type] !== "off"));
|
|
4918
4961
|
const exceptions = applyExceptions(result.infractions, options.ignoreExceptions ? [] : policy.config.exceptions, /* @__PURE__ */ new Date(), existingCounts);
|
|
4919
4962
|
result.infractions = exceptions.findings;
|
|
4920
4963
|
result.suppressedFindings = exceptions.suppressedFindings;
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-roast",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Stops AI agents (and rushed humans) weakening tests, coverage gates or CI to get a green build. Hook for Claude Code, Codex, Cursor, Gemini CLI, Copilot, Droid; pre-commit; GitHub Action. Offline.",
|
|
5
5
|
"mcpName": "io.github.Nari-Ab/agent-roast",
|
|
6
6
|
"type": "module",
|