Repository navigation
Conversation
Adds a .claude/ library for testing finbot-ctf with Claude Code, plus a standalone pre-merge script that works without Claude. - scripts/verify_change.py: maps changed files to the tests that cover them, validates changed challenge YAML (schema, detector registered, config accepted, prerequisites exist), runs the affected tests, and with --coverage lists changed lines no test runs. Exit codes 0/1/2 so it can gate CI. - Skills: verify-change, detector-tests, agent-tests, acceptance-criteria, qa-ticket. - Agents: test-writer, bug-hunter, requirements-reviewer. No application code changes.
… key CI can't hold model keys (fork PRs get no secrets), so agents write tests on the contributor's machine and CI only checks the result. - verify_change.py: in GitHub Actions, write the verdict and test gaps to the job summary, each gap with its testing guide and a prompt that works in any AI assistant. --json writes the same report to a file. - CI: new verify-change job (LLM_PROVIDER=mock, no secrets) that uploads the gap report. Non-blocking until three stale tests in test_specialized_agents.py are fixed. - AGENTS.md: entry point for Codex, Copilot, Cursor, Gemini CLI and others, pointing at the same guides Claude Code loads as skills. - .claude/README.md: "Getting started" for contributors.
- Don't report CLEARED when no tests ran: docs-, config- or
template-only changes now get NO TESTS APPLY.
- --e2e only runs when tests/e2e has test files; an empty suite no
longer turns into BLOCKED.
- Deleted modules: run the tests that still import them.
- Escape the module name in the test-discovery regex; a file name with
"(" crashed the whole check.
- Treat file names and error text from a PR as untrusted: neutralise
them in the job summary, and never put an unusual file name into an
AI prompt.
- CI job: pull requests only, compare with the PR's base branch,
read-only token.
- Docs: say what CLEARED and coverage do and don't promise, warn about
running agents on someone else's .claude/, and use `uv run python`
everywhere.
- Coverage ranges only bridge gaps with no executable code. Unchanged code that also didn't run no longer gets swept into a "not run" range. - Test discovery also finds tests that import a module's classes or functions through its package __init__, on one line or in parentheses. - A detector that can't be created reports the exception type, so an import error isn't mistaken for a bad detector_config.
…ey can fail - tests/unit/scripts/test_verify_change.py: 26 tests for line ranges, test discovery, untrusted file names, every verdict and exit code, the job summary and JSON report, and seven end-to-end runs in a throwaway git repository (docs-only, covered, untested, broken, uncovered, deleted, UI change with no mobile suite). A mutation run planting 12 bugs in the script: all 12 caught. - verify_change.py: changed scripts run the tests that load them by path, so the check now tests itself. Scripts aren't required to have tests. - test-writer: after tests pass, plant three bugs and confirm the tests catch each. An agent-written suite with 100% coverage caught only 7 of 10 planted bugs until its assertions were tightened. - Guides: assert whole results, not samples; make assertions specific to their branch; agent-tests also patches agents.utils.event_bus, stubs MCP servers and builds a SessionContext without a database. - acceptance-criteria and requirements-reviewer: read issues through the public API when gh isn't installed, and stop rather than guess when the issue can't be fetched.
…GitHub without gh
The job installed unpinned packages and skipped gspread, which the tests' conftest loads as a plugin, so pytest could not start. Install exactly what uv.lock pins instead: that includes gspread (loaded, never used: nothing reports to Sheets without --google-sheets and credentials) and keeps the job safe from breaking releases. Reproduced in a clean clone: 26 passed, CLEARED.
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds test creation agents for finbot: a pre-merge check finds which changes have no tests, and a set of guides and agents helps contributors write those tests the way this repo already does. CI runs the check with no model and no secrets, so it works on pull requests from forks.
No application code changes.
Why
tests/unit/agents/test_specialized_agents.pyhave failed onmainsince they were merged, and nothing caught it, because CI never runs that file.What's in this pull request
scripts/verify_change.py--coveragelists the changed lines no test runs. Verdicts:CLEARED,NEEDS TESTS,BLOCKED,NO TESTS APPLYtests/unit/scripts/test_verify_change.pyverify-changeLLM_PROVIDER=mock, no secrets. Writes the test gaps to the job summary with a ready-made prompt for each, and uploads a JSON reportAGENTS.md.claude/skills/.claude/agents/test-writer,bug-hunterandrequirements-reviewer, each pinned to a modelHow it fits together
flowchart LR dev([Contributor]) -->|/verify-change| VC[verify-change skill] VC -->|runs| S[[scripts/verify_change.py]] S -->|untested files and lines| VC VC -->|one agent per gap, in parallel| TW VC -->|a test failed| QT subgraph agents [Agents: the workers] TW[test-writer<br/>Sonnet] BH[bug-hunter<br/>Opus] RR[requirements-reviewer<br/>Sonnet] end subgraph skills [Skills: the how-to guides] DT[detector-tests] AT[agent-tests] AC[acceptance-criteria] QT[qa-ticket] end TW -->|testing a detector| DT TW -->|testing an agent| AT BH -->|writes tests meant to break it| DT BH -->|writes tests meant to break it| AT BH -->|confirmed bugs| QT RR -->|turns an issue into criteria| AC other([Codex, Copilot, Cursor, Gemini]) -->|AGENTS.md| DT other -->|AGENTS.md| ATAgents write tests on the contributor's laptop. CI only runs the check and the committed tests, with no model and no key.
Tests are written locally and CI only checks them. The finished tests mock the model, so they run in CI at no cost.
Try it
Example output, run against an earlier change on
main:With Claude Code, run
/verify-change. With another assistant,AGENTS.mdpoints it at the same guides.How it was verified
main: it maps changes to their tests, validates challenge definitions and reports uncovered lines, as shown above.main:test-writerwrote 30 tests forcommunication.py. After two assertions were tightened, they caught 10 of 10 planted bugs.bug-hunterfound 5 issues inguardrail_prevention.py, including a webhook that blocks every call completing Carte Noire. These will be filed separately.requirements-reviewerwrote acceptance criteria for CD001-WEB-001: Auth routes unit tests #132 and flagged that the issue names a file that doesn't exist.7c79808and6db4c52.Decisions worth a look
continue-on-error) because of the three failing tests above. A follow-up fixes them so it can block..claude/is a project-level choice. If you'd rather not have it, the script, its tests, the CI job andAGENTS.mdstand on their own..claude/andAGENTS.md..claude/README.mdsays so.Follow-ups
test_specialized_agents.py, then make the job blockingguardrail_prevention.pyfindings