ClaudeMods
☰
ZH-CN
● 0 人在线 · 浏览 0 次
赞助提交作品
GitHub 仓库 · 发布者 ccdwyer

assertion-guardian

一个会拒绝削弱测试文件的 Claude Code 模组:包括删除或放宽断言、删除用例、添加跳过、吞掉失败、重写快照,以及通过 shell 篡改测试,并在提示符上方提供一次性允许覆盖栏。

已翻译

关于这个 mod

Assertion Guardian 是一个 Claude Code 插件,会阻止智能体通过削弱测试来让测试通过。在对测试文件执行任何 Edit、Write 或 NotebookEdit 之前,它会计算修改前后的完整文件;如果测试变弱,就拒绝这次修改。轻量扫描器会区分代码、注释和字符串字面量,因此文档字符串、注释或夹具字符串里的断言不会被计入。它能检测被删除的断言、被删除的测试用例、被修改或删除的预期值、被放宽或替换的精确匹配器、含糊匹配器、常量断言、极性反转、被删除的表格行、新增的跳过或聚焦标记、被注释掉的断言、被吞掉的失败、死分支、手工重写的快照、批量快照更新,以及通过 shell 修改的测试文件(rm、mv、sed -i、>、tee、git rm、find -delete、git checkout、内联 python -c/node -e)。删除的测试文件会通过 git 与最后一次提交的版本比较,因此从头重写也绕不过检查。拒绝信息会列出触发的信号,并告诉模型修复被测代码或询问用户。正常重构可以通过。可以通过提示符上方的一次性允许/关闭栏,或 /allow-test-change 命令覆盖;覆盖会绑定到精确的工具调用。测试文件通过常见命名模式和快照路径识别。钩子包括 session.start、command.run{allow-test-change}、tool.call 和 ui.render{AbovePrompt}。完全在本地运行,不联网,也不收集遥测;许可证为 MIT。

安装

请先查看作者 README 确认 marketplace 和插件名称;命令可能随仓库结构改变。

claude plugin marketplace add ccdwyer/assertion-guardian
claude plugin install assertion-guardian
原文 / README

Assertion Guardian

Assertion Guardian demo

The model is asked to change an expected value so a failing test passes. Assertion Guardian refuses and names the signal, the band above the prompt offers allow once, and the real bug gets fixed instead. MP4

A Claude Code mod that stops the agent from making tests pass by making the tests weaker.

Before any Edit, Write or NotebookEdit to a test file runs, the mod works out the whole file before and after the change. It refuses the change if the tests got weaker. A light scanner first separates code from comments and string literals, so an expect( inside a docstring, a comment or a fixture string is never counted as code.

| Signal | Example | |---|---| | Assertions removed | expect(...), assert, assert.strictEqual, XCTAssert*, assertThat, t.Error, #expect | | Test cases deleted | it(...), test(...), def test_*, func Test*, @Test | | Expected values changed or dropped | Jest, Node assert, unittest/pytest, AssertJ, chai, RSpec, XCTest: toBe(3) changed to toBe(4), toHaveBeenCalledWith(1) changed to toHaveBeenCalled(), an edited inline snapshot, assert f() == 1 changed to == 2 | | Exact matchers replaced or loosened | toBe(3) changed to toBeTruthy(), toBeGreaterThanOrEqual(3) or toBeCloseTo(3), toStrictEqual changed to toEqual, toBeGreaterThan(3) changed to toBeGreaterThanOrEqual(3) or toBeLessThan(3), toBeCloseTo(3) changed to toBeCloseTo(3, 0) | | Vague matchers added | toEqual(0) changed to toEqual(expect.any(Number)) | | Constant assertions added | expect(3).toBe(3), assert.strictEqual(3, 3), assert 3 == 3, XCTAssertEqual(3, 3), assert True | | Assertion polarity flipped | .toBe(3) changed to .not.toBe(3), strictEqual changed to notStrictEqual, assert x == 3 changed to != 3, is None changed to is not None | | Test data rows removed | a row deleted from a Go table test or a test.each table | | Skips added | it.skip, xit, it.todo, test.fail, this.skip(), @pytest.mark.skip/xfail, @Disabled, @Ignore, t.Skip, XCTSkip | | Focus added | .only / fit(, which silently drops every other test | | Assertions commented out | a line that held an assertion reappears as a comment | | Assertion failures swallowed | a try around an assertion whose catch / except / rescue doesn't rethrow, pytest.raises(AssertionError), expect(() => expect(...)).toThrow() | | Dead branches | if (false) { expect(...) }, return before the assertions | | Snapshot files rewritten by hand | *.snap, __snapshots__/ | | Snapshots updated in bulk | jest -u, npm test -- -u, --update-snapshots, cargo insta accept, INSTA_UPDATE=1, loki update, backstop approve, chromatic --auto-accept-changes. Wrappers such as env, npx, pnpm exec, pnpm --filter and python -m are looked through | | Test files changed through the shell | rm, mv, sed -i, perl -pi, > redirects, tee, git rm, rm -rf tests, find ... -delete, git checkout <rev> -- file, bash -c "...", one-off python -c / node -e scripts that name a test file |

Suppose a test file has been deleted and is then written again from scratch. The new version is compared with what git last committed, so deleting the file doesn't get around the check.

The refusal lists which signals fired. It tells the model to fix the code under test, or to ask you if it thinks the test itself is wrong.

Normal refactors pass: renaming the value under test, reformatting, reordering, moving assertions between tests, adding tests, switching quote style, swapping in stronger matchers, and changing toBe(null) to toBeNull().

Override: a band above the prompt shows the last blocked change, with allow once and dismiss buttons. Or run /allow-test-change, which runs immediately, even mid-turn. An allowance is tied to that exact call (same tool, same file, same content), so the model has to retry the identical change. A different edit to the same file is checked from scratch.

This is a guardrail, not a sandbox. It checks what tool calls say they'll do. A determined process could still change files some other way, such as a script that never names the test file.

Test files are recognised by *.test.*, *.spec.*, test_*.py, tests.py, *_test.{py,go,rb,ex,dart}, *_spec.rb, *Tests.swift, *Test.{kt,java,cs}, and snapshot paths. Code files under __tests__/, test/, tests/ or spec/ count too, except those inside fixtures/, support/, helpers/, factories/, __mocks__/ or testdata/.

Install

/plugin marketplace add ccdwyer/claude-mods
/plugin install assertion-guardian@ccdwyer-mods
/reload-plugins

Develop

claude plugin validate .
claude plugin test .

What it hooks

Events this mod hooks, as claude plugin validate reads the module:

  • session.start
  • command.run{command=allow-test-change}
  • tool.call
  • ui.render{component=AbovePrompt}

Engine calls it makes: $.command.register, $.fs.exists (via previous), $.fs.read (via previous), $.process.run (via previous), $.state.get, $.state.set, $.ui.resolve.

A tool.call hook sits in the middle of every tool call: it can see the call, refuse it, or add context to its result. This mod uses that only for the behaviour described above.

Privacy

It runs entirely on your machine. It sends nothing over the network. It runs git locally to read the previous version of a test file.

The mod collects no analytics or telemetry, and its author receives no data from it.

Full policy: PRIVACY.md.

License

MIT

更多类似作品