The deep-qa agent
The deep-qa agent
Section titled “The deep-qa agent”deep-qa writes tests, proves they can fail, fixes what they catch, and stops. It is the only
agent here that is allowed to change lib/, and it is deliberately hard to satisfy.
/qa lib/presentation/widgets/components/floating_nav_bar.dart # one target/qa diff # everything uncommitted/qa audit test/presentation/bloc/search_screen # are these tests real?/qa gate # just run the gate and fixWith no argument it asks. It does not guess a target.
Why it exists
Section titled “Why it exists”The test suite went from nothing to 2,297 tests in two weeks, and then the audit found tests
that could not fail. One asserted isA<WatchlistScreenCollectionLoadedState>() and passed
happily while copyWith blanked a watchlist’s artwork on every save — bug-log #27. The
assertion was true for a correct state and equally true for a broken one.
So the agent’s contract is not “add tests”. It is “add tests and prove each one can fail”. The proof is mutation: break the code on purpose, watch the new test go red, put the code back. A test that stays green through its own mutation is deleted, not kept.
The loop
Section titled “The loop” /qa <target> │ ▼ SCOPE ─────── record the before row: git status, analyzer count, coverage floors. │ Open a ledger in the scratchpad; update it every phase, so a long │ run can be resumed by reading one file instead of starting over. ▼ CONTRACT ──── what is this SUPPOSED to do? From the API fixtures, the .arb │ strings, the entity, the sibling tests — never from the │ implementation. Reading the code and writing it back down as an │ assertion produces a test that agrees with any behaviour at all. ▼ RISK ──────── list the failure modes BEFORE writing a line. Missing API field, │ null, [] where an object was promised, empty list, offline, │ cache hit, 320-wide screen, longest string, signed out, disposed │ mid-flight. This is the step that finds bugs; the tests just │ record what it found. ▼ WRITE ─────── one behaviour per test, named as a sentence, asserting the │ payload rather than the state's type, using test/helpers/. ▼ MUTATE ────── break the code. Every new test must go red. Restore with │ git checkout --. Re-run green. ──┐ │ │ survived? the test │ For widgets the blunt version works: │ is vacuous — back │ swap pumpApp's child for an empty box │ to WRITE │ and list who still passes. ──┘ ▼ TRIAGE ────── a red test is exactly one of three things: │ a test bug → fix the test │ a real defect → fix lib/, or escalate if off-limits │ intended behaviour→ pin it, flag it, change nothing │ Never green by deletion. ▼ GATE ──────── tool/quality.sh fast. 10 format · 11 analyzer · 12 generated │ stale · 13 tests · 14 coverage · 15 goldens · 16 e2e. ▼ LAND ──────── commit (fix + its test together, citing bug-log #N), report, stop.What it will not do
Section titled “What it will not do”It fixes clear, contained defects in lib/ — a missing null guard, a dropped field, a widget
that overflows, a state carrying the wrong payload. It stops and hands you a filled-in
escalation, with the failing test as evidence, for:
- the player (
lib/presentation/widgets/player/,ottstack_player,kvideo) - payments and IAP
- auth, sessions and
EncryptionHelper - Hive field numbers and
typeIds - the shape of
injection_container.dart local_lib/and anything generated
It also never pushes, never opens a PR, never amends an existing commit, never removes a null check, and never lowers a baseline.
Where it lives
Section titled “Where it lives”One source, two runtimes. The agent file is real; everything else is a symlink, the same shape
as the vendored flutter-* skills.
.claude/agents/deep-qa.md the agent ◄── .pi/agents/deep-qa.md .agents/skills/deep-qa/SKILL.md the loop ◄── .claude/skills/deep-qa references/ rules · recipes · harness · traps · gate .claude/commands/qa.md /qa in Claude Code .pi/prompts/qa.md /qa in piThe frontmatter is name and description and nothing else. That is the intersection of the
two parsers: tools would need lowercase names in pi and capitalised ones in Claude Code, and
model means a different thing in each. Omitting both gives full tools and the inherited model
on both sides, from one file.
The references are the part worth maintaining. traps.md in particular is the accumulated cost
of every afternoon someone lost to VideoPlayerController.dispose() hanging a whole test file,
or to the 800×600 default surface silently clamping a layout assertion. When something new bites,
add it there.