Reference

The config schema, how to add a stack, and what every agent and skill does. If you are looking for what this is and why, start at the overview.

qa.config.yml

One file at the project root. Every agent reads it before doing anything, so a wrong value here makes everything downstream confidently wrong.

KeyMeaning
project.nameUsed in report headers and generated file names
project.glossaryOptional. A markdown file of domain terms, so generated tests speak the business's language
stacks.<platform>.profileFolder name under profiles/. This is the line that picks your language and runner
stacks.<platform>.rootWhere the test code for this stack lives, relative to the project root
stacks.<platform>.base_url_envName of the env var holding the base URL. Agents never hardcode a URL
stacks.mobile.platforms[android, ios]
stacks.mobile.appPath or build artifact URL, per platform
ci.providergithub-actions, gitlab-ci, jenkins, circleci, none. Picks which template /qa:init copies out of the profile
tracker.providerjira, github-issues, linear, none. Where requirements come from and bugs go
tracker.project_keye.g. SHOP
requirements.sourceconfluence, markdown, github-issues, jira
requirements.pathFolder of requirement documents, when the source is markdown
reporting.artifactsWhere the runner drops artifacts. The maintainer and auditor read these
policies.test_id_attributeThe attribute the author agent prefers for a stable hook
policies.max_test_runtime_sA test slower than this gets flagged in an audit
policies.forbidHouse rules. Not advice, see below

policies.forbid

Five rules are understood out of the box, and qa-policy checks all five. Anything else you put in the list is read by the agents but not machine checked.

RuleCatchesCheck
hard_sleepswaitForTimeout, time.sleep, Thread.sleep, implicitly_wait, driver.pause, cy.wait(500)exact
absolute_xpath//div[3]/span, anything rooted at /htmlexact
conditional_assertionsAn assertion inside a branch, so the test can pass by skipping its own checkheuristic
no_assertionA test that drives the app and never checks anythingheuristic
shared_mutable_statedescribe.serial, module level mutables, enforced orderingheuristic
The heuristics label themselves in the output. They are meant to catch the obvious case fast, inside an edit hook, before it lands. Anything subtler is the auditor's job, and it has a model to reason with.

Profiles

A profile teaches the agents one stack. It holds no QA reasoning, just the mechanics.

profiles/<name>/
  profile.yaml      commands, layout, templates, selector priority
  CONVENTIONS.md    how to write a test here, with real code
  templates/        skeleton files the author agent copies
  ci/               one pipeline file per CI provider

What ships

ProfileLanguageRunnerPlatformState
ts-playwrightTypeScriptPlaywrightwebdemo runs in CI
py-pytest-playwrightPythonpytest + Playwrightwebdemo runs in CI
mobile-appium-wdioTypeScriptWebdriverIO + Appiummobiletypechecked in CI, run locally on a device
_templateanyanyanyblank starting point

Adding one

cp -r profiles/_template profiles/java-selenium-junit5
# fill in profile.yaml, name must match the folder
# write CONVENTIONS.md with real code, this is the file that decides
# whether generated tests look like they belong in your repo
python3 scripts/validate_profiles.py

Then point a project at it with stacks.web.profile: java-selenium-junit5. No agent changes.

Java plus Selenium plus JUnit 5 is deliberately not shipped. It is the obvious next profile and writing it from the template is the fastest way to find out whether this design holds up.

Who reads what

FileRead byFor
commandsmaintainer, auditorRunning the suite, rerunning one test
layoutauthor, auditorWhere a new file goes, which files are tests
templatesauthorStarting a test or page object
selector_priorityauthor, exploratory guidePicking a locator
CONVENTIONS.mdauthor, maintainerEverything the template cannot express
ci//qa:initDropping a pipeline into a new project

Command placeholders: {file} for a test file path, {title} for a test name, {tag} for a tag or marker expression.

Agents

Three. An agent exists where the work reads a lot of files and needs no conversation. Everything interactive is a skill running in the main thread, because a subagent cannot ask you a question.

test-author

Writes tests and page objects in the active profile's style. Runs what it wrote, then breaks it on purpose to confirm it can fail. Reports which design rows it did not write and why.

test-maintainer

Fixes failing and flaky tests and refactors duplication. Triages through five causes before editing. Fixes the cause once rather than the symptom four times.

suite-auditor

Scores a suite against a nine area rubric with evidence, ranks findings by cost over effort, and changes nothing.

What they refuse to do

  • Author: promote a unit row to e2e because e2e is the layer it can reach. Invent an expected result. Write a row the design said not to automate.
  • Maintainer: add a sleep, widen an assertion to make it pass, add retries to hide a flake, or delete a test to clear a red build.
  • Auditor: change anything, publish a total score, or report a finding without a path and a line behind it.

Those lists are the interesting part of each agent. The rest is method.

Skills

Workflows

SkillDoesHands off to
/qa:initWorks out the stack from the repo, writes qa.config.yml, drops in CIinline
/qa:profileReads an existing suite and drafts a profile describing what that repo actually does, into <project>/profiles/inline, it asks which pattern wins where the suite disagrees with itself
/qa:refineTicket to acceptance criteria, ambiguities, risk. Asks the blocking questionsinline, it needs a conversation
/qa:designRisk scored coverage matrix, layer per row, plus what is not coveredinline
/qa:exploreSession based exploratory testing through the browser or device MCPinline, it needs a conversation
/qa:generateWrites the automatable rowstest-author past three rows
/qa:fixTriage then fixtest-maintainer past three failures
/qa:auditScore and ranksuite-auditor, always
/qa:prevalidateRuns the policy check against what is staged, same rules as the edit hookinline, it only reports

Reference skills

Model invoked, pulled in by the workflows and usable on their own.

SkillHolds
qa-configHow to resolve the project setup and what to do when it fails. Every other skill leans on this
test-design-techniquesEquivalence partitioning, boundary values, decision tables, state transitions, pairwise. Which technique for which shape of requirement, risk scoring, and choosing a layer
flake-triageThe five causes of a red test in the order they are actually likely, and the rerun protocol (qa-rerun runs it) that tells a flake from a defect
prevalidateRuns qa-policy against what is staged. Standalone outside Claude Code too, see setup
bug-reportA report shape that avoids the round trip, and how to file to the configured tracker
/qa:profile is the brownfield entry point. A shipped profile knows how to run your tests. It does not know how you write them, and generated code that fights your reviewers is worse than no generated code. See the setup page.
There is no page-object-authoring skill. That knowledge lives in each profile's CONVENTIONS.md, where it can be stack specific and correct, instead of in a skill where it would have to be generic and vague.

Tools and wiring

Three commands

All stdlib only Python, because they run on the critical path of every task and adding a pip install there would be a bad trade for a few fields of YAML. All three also run standalone, outside Claude Code, from a bare clone.

qa-context [stack] [--paths]     resolve config + active profile + conventions
qa-policy  <file>... | --stack   check against policies.forbid
qa-policy  --changed-only <file> only lines this working tree added or changed
qa-rerun   <file> --title "..."  run a test N times alone/suite/parallel, read the pattern

One hook

PostToolUse on Write and Edit runs qa-policy --hook. A test written with a forbidden pattern in it exits 2, which is the code that sends the message back to be fixed rather than only printing it. Outside a project with a config, it does nothing.

--hook implies --changed-only, and that is what makes it usable on a repo that already has a suite. It reports violations on lines the edit touched, not on the whole file. Dropping this into a codebase with 200 existing sleeps and then failing every edit to every file containing one is not a guardrail, it is a wall, and it gets the hook deleted on day two. Pre-existing violations are still real and still surface in an audit, where a decision about them can actually be made. A file git has never seen is checked in full, since there is nothing to compare it against.

MCP

ServerForState
Playwright MCPDriving a real browser during exploratory sessions, and finding selectors before writing a testships enabled in .mcp.json
Appium or WebdriverIO MCPThe mobile equivalentopt in, needs a local Appium server and a device
AtlassianPulling requirements out of Jira and Confluence, filing bugs backopt in, per tracker.provider
gh CLIGitHub issues and PRsno MCP needed

Checks

python3 scripts/validate_profiles.py   every profile.yaml and every qa.config.yml
python3 scripts/test_qa_context.py     the config parsing
python3 scripts/test_qa_policy.py      that qa-policy fires on what it claims to
python3 scripts/test_qa_rerun.py       that qa-rerun resolves the right command and reads the pattern right

All three run in CI, along with both web demo suites against the live site and a typecheck of the mobile example.

The third one matters most. A policy checker that never fires reads as coverage and is worse than not having one, so its checks include the shapes it must catch and the shapes it must stay quiet on.