Skip to content

Quality Gates

Four gates protect main. Gates 2, 3 and 4 are automated. Gate 1 is you: the manual test you do in your browser at the end of each epic.

Run /quality-check at any time to see the current status of the automated gates.

GateWhat it checksWho runs it
1. FunctionalThe epic works the way you describedYou, at the manual test
2. SecurityNo secrets in the code, no serious dependency vulnerabilities, and no unsafe server-side patternsAutomated
3. Code qualityTypeScript compiles, lint rules pass, formatting is consistent, and the production build succeedsAutomated
4. TestingEvery automated test passes, and the tests avoid known bad patternsAutomated
WhenWhat runs
After each storyA quick check: lint rules and test quality. Claude has already run the unit tests and a type check.
At the end of each epicThe full automated gates, a code review of the whole epic, and the end-to-end browser tests against a production build
On GitHubGates 2, 3 and 4, on every pull request to main and on every push to main that changes the app

Running the full gates once per epic, rather than after every story, keeps the build fast. The quick per-story check still catches most problems while the story is fresh.

You check that the epic does what you asked for, using the running app the way a user would. There are no automated checks in this gate. Claude gives you a checklist of what to try. See Manual Test Checklists.

This gate scans for secrets such as passwords, API keys and tokens committed by mistake. It also checks for unsafe patterns in server-side code and scans the third-party packages your app ships.

A Critical or High dependency vulnerability fails the gate. Medium and Low ones are reported but don’t fail it. The report names the package and the advisory, and Claude can usually fix it by updating the dependency.

Compliance obligations, such as POPIA, are raised with you during Intake rather than scanned for. If your app handles personal data, reviewing it for compliance is part of your manual test.

Sometimes a serious advisory has no fix available from the package’s authors. To stop that one advisory failing every run, add a time-limited exception to web/dependency-audit-exceptions.json. Create the file if it doesn’t exist.

{
"advisories": {
"GHSA-1234-abcd-5678": {
"reason": "No upstream fix yet. Not reachable in our usage. Tracking: <link>",
"expires": "2026-12-01"
}
}
}
FieldWhat it means
KeyThe advisory ID from the gate’s report
reasonWhy the advisory is allowed for now. It appears in the report.
expiresThe date the exception ends, as YYYY-MM-DD. After it, the advisory fails the gate again.

Every other vulnerability still fails the gate. The file is part of your code, so each exception shows up in pull requests for your team to review. Use exceptions only when no fix exists, not to hide an advisory you could fix by updating the package.

This gate checks that the code is valid TypeScript, passes the lint rules, is consistently formatted, and builds for production. It’s fully automated.

A pre-commit check also runs the lint and formatting rules on every commit, so most problems never reach the gate.

This gate runs the unit and integration tests, and checks the tests themselves for known bad patterns. A test that can never fail, for example, fails this gate.

The end-to-end browser tests run on your machine at the end of each epic, not on GitHub. They also cover accessibility, such as colour contrast and focus order, and anything time-based, such as session timeouts.

You don’t need to go looking for gate results. While Claude is shipping an epic, it waits for the GitHub checks and tells you directly if one fails, with the actual error.

To see the results yourself, open the Actions tab of your repository on GitHub and pick a run. Each gate has its own page listing every check it made. Failures also appear on the pull request’s Checks tab.

The gates don’t post comments on your pull requests. If one fails, GitHub emails you once, with a link to the details.