ClaudeMods
☰
ZH-TW
● 0 人在線上 · 瀏覽 0 次
贊助提交作品
GitHub 儲存庫 · 發布者 ccdwyer

assertion-guardian

一個會拒絕削弱測試檔案的 Claude Code 外掛:涵蓋移除或放寬斷言、刪除案例、加入跳過、吞掉失敗、重寫快照,以及透過 shell 篡改測試,並在提示列上方提供一次性覆寫列。

已翻譯

關於這個 mod

Assertion Guardian 是一個 Claude Code 外掛,會阻止代理程式靠削弱測試來讓測試通過。在對測試檔案執行任何 Edit、Write 或 NotebookEdit 前,它會計算修改前後的完整檔案;如果測試變弱,就拒絕這次修改。輕量掃描器會分開程式碼、註解與字串常值,因此文件字串、註解或 fixture 字串中的斷言不會被計入。它能偵測移除的斷言、刪除的測試案例、變更或刪掉的預期值、放寬或替換的精確比對器、模糊比對器、常數斷言、極性反轉、移除的表格列、新增的 skip 或 focus 標記、被註解掉的斷言、吞掉的失敗、無效分支、手動重寫的快照、批次快照更新,以及透過 shell 變更的測試檔案(rm、mv、sed -i、>、tee、git rm、find -delete、git checkout、內嵌 python -c/node -e)。刪除的測試檔案會透過 git 與最後一次提交的版本比較,所以從頭重寫也無法繞過檢查。拒絕訊息會列出觸發的訊號,並告訴模型修正被測程式碼或詢問使用者。一般重構可以通過。可用提示列上方的一次性允許/關閉列,或 /allow-test-change 指令覆寫;覆寫會綁定到精確的工具呼叫。測試檔案會依常見命名模式與快照路徑辨識。Hooks 包含 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

更多類似作品