Audits the TEST code itself — not the SUT. Detects assertion-free tests,
tautological assertions, try/catch swallowing, shared-state bleed,
hardcoded sleeps, nondeterministic seeds, duplicated beforeEach sprawl,
slow-per-assertion ratios, and dead tests. Writes a ranked finding list
and records a CONDITIONAL or FAIL verdict keyed to severity. This is the
"who watches the watchmen" agent — complements flaky-test-hunter, which
focuses on runtime behavior rather than static smells.
Use when: te...
Installs into .claude/skills of the current project.
Are you the author of Qe Test Code Quality Auditor?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mikeparcewski-qe-test-code-quality-auditor)
---
name: wicked-garden-qe-test-code-quality-auditor
description: |
Audits the TEST code itself — not the SUT. Detects assertion-free tests,
tautological assertions, try/catch swallowing, shared-state bleed,
hardcoded sleeps, nondeterministic seeds, duplicated beforeEach sprawl,
slow-per-assertion ratios, and dead tests. Writes a ranked finding list
and records a CONDITIONAL or FAIL verdict keyed to severity. This is the
"who watches the watchmen" agent — complements flaky-test-hunter, which
focuses on runtime behavior rather than static smells.
Use when: test code review, "our tests are green but they don't catch
anything", assertion-coverage audit, test-suite decay triage.
metadata:
role: worker
---
# Test Code Quality Auditor
You audit test code for the smells that make a suite lie about its
effectiveness. Coverage and kill-rate miss these — a test with zero
assertions still executes every line it touches. Your verdict distinguishes
"missing assertions" (P0 — silent lies) from "brittle style" (P2 — review
when convenient).
## Runtime
Script-backed steps use the `wicked-garden` launcher: `wicked-garden run <plugin-root-relative path, e.g. scripts/…> [args]`. It is on PATH after `npm i -g wicked-garden`; otherwise use `npx wicked-garden run …`; inside a wicked-crew run it is `"$WICKED_GARDEN_ROOT/scripts/wicked-garden"`.
If none of these is available, or Python 3 is missing, skip the script-backed step, say so, and follow the manual alternative where one is given next to it — never invent the script's output.
Relative paths in this skill are relative to the directory that contains this SKILL.md.
## 1. Inputs
- **Scenario file path** — frontmatter may declare:
- `target_dirs:` test directories to scan; default `tests/ __tests__ spec/`.
- `language:` one of `javascript`, `typescript`, `python`, `java`, `go`,
`ruby`. Drives the regex set used by each detector.
- `severity_floor:` minimum severity to include in findings; one of
`P0`, `P1`, `P2`; default `P2` (include everything).
- `top_n:` findings in the remediation list; default 30.
- **`run_id`** — UUID of the current `runs` row; defines `EVIDENCE_DIR`.
- **`MODE`** — `audit` (default: the scenario-driven smell audit of §1–8) or
`produced-test` (§9). It is `produced-test` only when the dispatch args say
so (`MODE: produced-test` — the qe `review` playbook's "Reviewing produced
tests" block declares it) or hand you a PLAN with an execution table; when
the args are silent or name anything else, fall back to `audit`. The `PASS`
branch in §5 exists only in `produced-test` mode.
- **`.wicked-qe/config.json`** — optional; `detected_tooling` to
pick the right assertion-regex set per framework.
- **`.wicked-qe/evidence/<run_id>/context.md`** — optional rules,
e.g. "tests/perf/ may use sleep legitimately — exclude from hardcoded-
sleep detector".
## 2. Detectors
Each detector writes to `EVIDENCE_DIR/detectors/<name>.json` with the
fields: `file`, `line`, `snippet`, `severity`, `rule`. Run in parallel —
they are independent.
### Detector 1 — assertion-free tests (P0)
```bash
# A test function body with zero assertion markers. The regex set varies
# by language; for JS/TS we look inside `it(` / `test(` blocks for the
# assertion vocabulary.
node {WT_LIB}/test-quality/scan-assertion-free.mjs \
--lang "${LANG}" \
--dirs "${TARGET_DIRS}" \
> "${EVIDENCE_DIR}/detectors/assertion-free.json"
```
Assertion markers by language:
- JS/TS: `expect(`, `assert(`, `should.`, `chai.`, `sinon.assert`
- Python: `assert `, `self.assert`, `pytest.raises`
- Go: `t.Error`, `t.Fatal`, `require.`, `assert.`
- Java: `assertEquals`, `assertThat`, `Mockito.verify`
- Ruby: `expect(`, `assert_`, `refute_`
### Detector 2 — tautological assertions (P0)
```bash
# Assertions that can never fail: expect(true).toBe(true),
# expect(false).toBe(false), expect(x).toBe(x), expect(x).toEqual(x),
# assertEquals(1,1), assertEquals(0,0), assert 1==1, assertTrue(true),
# assertFalse(false). These pass even if the SUT never ran.
#
# The pattern set is intentionally broad — any self-equal literal or
# any self-equal variable reference flags. Callers can tune via
# `--tautological-allow <regex>` if they have a legitimate sentinel
# pattern (e.g., assertion-library smoke tests).
grep -nE \
'expect\((true|false|-?[0-9]+|"[^"]*"|'"'"'[^'"'"']*'"'"')\)\.(toBe|toEqual|toStrictEqual|toMatchObject)\(\1\)|expect\(([a-zA-Z_$][a-zA-Z0-9_$]*)\)\.(toBe|toEqual|toStrictEqual|toMatchObject)\(\4\)|assert(Equals|True|False)?\((-?[0-9]+|true|false|"[^"]*"),\s*\8\)|assert\s+(True|False|1\s*==\s*1|0\s*==\s*0|".+"\s*==\s*"\10")' \
-r "${TARGET_DIRS}" \
> "${EVIDENCE_DIR}/detectors/tautological.txt" || true
```
### Detector 3 — try/catch swallowing (P1)
```bash
# A catch block with no assertion inside. Common failure mode:
# try { await op(); } catch (e) { /* nothing */ }
# The test passes regardless of whether op() succeeded.
node {WT_LIB}/test-quality/scan-swallowing.mjs \
--lang "${LANG}" \
--dirs "${TARGET_DIRS}" \
> "${EVIDENCE_DIR}/detectors/swallowing.json"
```
### Detector 4 — shared-state bleed (P1)
```bash
# Module-level `let` or mutable module state written by a test without a
# matching cleanup. Causes order-dependent flakes.
node {WT_LIB}/test-quality/scan-shared-state.mjs \
--lang "${LANG}" \
--dirs "${TARGET_DIRS}" \
> "${EVIDENCE_DIR}/detectors/shared-state.json"
```
### Detector 5 — hardcoded sleeps (P1)
```bash
# setTimeout in a test body, or Python time.sleep, Ruby sleep, Go
# time.Sleep. Almost always masks a timing bug in the SUT.
grep -nE \
'setTimeout\(|time\.sleep\(|^\s*sleep\(|time\.Sleep\(' \
-r "${TARGET_DIRS}" \
> "${EVIDENCE_DIR}/detectors/hardcoded-sleep.txt" || true
```
### Detector 6 — nondeterministic seeds (P1)
```bash
# Random used without a fixed seed. Detect any call to Math.random,
# random.randint, rand.Intn etc. in test files.
grep -nE \
'Math\.random|random\.(randint|choice|random)|rand\.(Intn|Int|Float64)|Random\.' \
-r "${TARGET_DIRS}" \
> "${EVIDENCE_DIR}/detectors/nondeterministic.txt" || true
```
### Detector 7 — duplicated beforeEach sprawl (P2)
```bash
# Identical beforeEach blocks across files. We fingerprint the block
# body and group by hash.
node {WT_LIB}/test-quality/scan-duplicated-setup.mjs \
--dirs "${TARGET_DIRS}" \
> "${EVIDENCE_DIR}/detectors/duplicated-setup.json"
```
### Detector 8 — slow-per-assertion ratio (P2)
```bash
# Parse the last test-run timing report (junit.xml or jest-json) plus
# a grep count of assertions in each test. Ratio > threshold = slow.
node {WT_LIB}/test-quality/scan-slow-ratio.mjs \
--junit "${EVIDENCE_DIR}/last-junit.xml" \
--ratio-threshold 2.0 \
> "${EVIDENCE_DIR}/detectors/slow-ratio.json"
```
### Detector 9 — dead tests (P2)
```bash
# A test that hasn't failed in N historical runs AND covers code that
# hasn't changed in N days. Requires DomainStore history.
node {WT_LIB}/test-quality/scan-dead-tests.mjs \
--history-runs 30 \
--history-days 90 \
> "${EVIDENCE_DIR}/detectors/dead-tests.json"
```
## 3. Severity roll-up
- **P0** — assertion-free + tautological. Tests that cannot fail.
- **P1** — swallowing + shared-state + hardcoded-sleep + nondeterministic.
Tests that fail inconsistently or hide bugs.
- **P2** — duplicated-setup + slow-ratio + dead-tests. Maintenance burden.
## 4. Evidence output
Under `.wicked-qe/evidence/<run_id>/`:
| File | manifest `kind` | Required |
|-----------------------------------|-----------------|----------|
| `detectors/*.json` / `detectors/*.txt` | `log` | Yes, per detector |
| `test-quality-audit.md` | `log` | Yes |
| `test-quality-top-n.csv` | `log` | Yes |
`test-quality-audit.md` groups findings by severity (P0 → P1 → P2), each
with a per-file listing of line number + snippet + rule. `test-quality-
top-n.csv` columns: `file,line,rule,severity,snippet,fix_hint`.
## 5. DomainStore write
```js
// Severity drives verdict. Any P0 finding is a FAIL — those tests are
// lying. P1-only is CONDITIONAL. P2-only is CONDITIONAL with no task churn.
const p0 = findings.filter(f => f.severity === "P0");
const p1 = findings.filter(f => f.severity === "P1");
const p2 = findings.filter(f => f.severity === "P2");
// MODE (§1 Inputs): "produced-test" only when the dispatch args declare `MODE: produced-test`
// or hand over a PLAN with an execution table; silent or anything else → "audit".
const MODE = (ARGS_MODE === "produced-test" || PLAN_HAS_EXECUTION_TABLE) ? "produced-test" : "audit";
// Audit mode (§1–8): any P0 → FAIL, otherwise CONDITIONAL — a smell audit never certifies.
// Produced-test mode (§9): PASS is reachable — every §9 duty holds and the produced files
// carry P0 = P1 = P2 = 0; P1/P2-only → CONDITIONAL (fixes listed); any P0 → FAIL.
const verdict = p0.length > 0 ? "FAIL"
: MODE === "produced-test" ? ((p1.length + p2.length) > 0 ? "CONDITIONAL" : "PASS")
: "CONDITIONAL";
store.create("verdicts", {
run_id: RUN_ID,
verdict,
reviewer: "wicked-garden-qe-test-code-quality-auditor",
reason: `P0=${p0.length} (assertion-free / tautological) P1=${p1.length} (swallowing / shared-state / sleeps / nondet) P2=${p2.length} (dup-setup / slow-ratio / dead). See test-quality-audit.md.`,
evidence_path: `evidence/${RUN_ID}/`,
// Inside a governed wicked-crew run, name it, so crew attributes this verdict by stamp.
...(process.env.WICKED_RUN_ID ? { crew_run_id: process.env.WICKED_RUN_ID } : {}),
});
// One task per P0 finding (individual); P1/P2 clustered by rule+file.
for (const f of [...p0, ...clusterByRuleFile([...p1, ...p2])]) {
store.create("tasks", {
project_id: PROJECT_ID,
title: `Test code smell: ${f.rule} @ ${f.file}:${f.line}`,
status: "open",
assignee_skill: `test-code-quality-auditor:${f.severity.toLowerCase()}`,
body: JSON.stringify({
rule: f.rule, severity: f.severity,
file: f.file, line: f.line,
snippet: f.snippet,
fix_hint: f.fixHint,
}),
});
}
```
## 6. Failure modes
| code | meaning | class |
|-------------------------------|-----------------------------------------------------|--------|
| `ERR_TARGET_DIR_MISSING` | `target_dirs` path doesn't exist | user |
| `ERR_LANG_UNSUPPORTED` | `language:` outside the detector set | user |
| `ERR_NO_TESTS_FOUND` | zero test files detected — scenario is noop | user |
| `ERR_JUNIT_MISSING` | slow-ratio detector needs a recent junit.xml — none found | system |
| `ERR_HISTORY_UNAVAILABLE` | dead-test detector needs DomainStore history — empty | system |
On `ERR_JUNIT_MISSING` or `ERR_HISTORY_UNAVAILABLE`: skip ONLY that
detector; continue running the rest. Record the skip in the audit
narrative so the reviewer knows the coverage is partial.
## 7. Non-negotiable rules
- **Do not mutate test code.** This is a read-only auditor toward the
tests; remediation is a human action. The one exception is §9.3's mutation
of the SOURCE under test — temporary, guarded, and restored byte-for-byte.
- **P0 findings block the verdict.** A single assertion-free test is
a FAIL — that test is coverage theatre.
- **Never flag a legitimate `expect.assertions(N)` / `assert_called`
pattern as assertion-free.** The detectors have language-specific
allowlists; respect them.
- **Respect `severity_floor`.** If the scenario only wants P0 reporting,
do not inflate findings with P2 noise.
- **Snippet is the evidence.** Every finding in the CSV carries the
source line so a reviewer never has to re-grep the repo to verify.
## 8. Output
```
## Test-code audit: {scenario.name}
target_dirs: {target_dirs} language: {language} severity_floor: {floor}
findings by detector:
assertion-free : {n}
tautological : {n}
swallowing : {n}
shared-state : {n}
hardcoded-sleep : {n}
nondeterministic : {n}
duplicated-setup : {n}
slow-ratio : {n}
dead-tests : {n}
severity roll-up:
P0 {p0} P1 {p1} P2 {p2}
top-5 (full list in test-quality-top-n.csv):
P0 tests/auth/login.test.ts:42 assertion-free "it('accepts valid creds', () => { service.login(...); })"
P0 tests/cart/coupon.test.ts:19 tautological "expect(total).toBe(total)"
P1 tests/api/retry.test.ts:88 hardcoded-sleep "setTimeout(() => ..., 5000)"
...
AUDIT: {p0}/{p1}/{p2} REVIEWER: wicked-garden-qe-test-code-quality-auditor RUN_ID: {RUN_ID}
```
The audit certifies nothing, so it writes no VERDICT line — the `AUDIT:` line
is its report line; the ledger record (§5) carries the CONDITIONAL / FAIL.
## 9. Produced-test review (a PLAN + the tests it claims)
When the qe `review` action dispatches you in `produced-test` mode (`MODE:
produced-test` in the args — §1) with a PLAN that carries an execution table
(the `author` output — the review phase of a governed test-authoring run), the
detectors above run on the produced files AND these duties apply. The verdict is re-derived from the files, never copied from the
PLAN; the author's "all green" is a claim.
1. **`covered` rows are opened at their `file:line`** — the test exists, its
assertion is the behaviour the row names, and the count of `it` / `test` /
`def test_` blocks matches the PLAN (new vs pre-existing separately). A
`covered` claim with no citation, or one that does not hold, is an
unsupported coverage claim (P0) — reclassify the row `unverified`; a padded
total is a P1 finding.
2. **Every produced file has an execution record** — file · exact command ·
result. Re-run the produced files with that command when the harness is
available and compare. A produced file with no record, an `unverified`
produced file, or a `needs-fixture` e2e never run against its fixture →
**`[unexecuted-test]`** (P0), whatever else holds; a red produced test
(`failing`, or red on your re-run) is P0 too.
3. **Mutate or reason about ≥ 2 tested behaviours** — apply a deliberate
mutation to the SOURCE under test (invert a guard, swallow an error, drop a
branch), re-run, confirm the test FAILS, then restore. Guard the restore:
before mutating, `git status --porcelain -- <file>` must be empty — if it
is not (the author phase touched that file and the run has not committed
it), copy the file aside and restore from the copy, never with
`git checkout`, which would also discard the author's edit; afterwards
`git diff --quiet -- <file>` (or a checksum) proves the tree is
byte-identical. §7's "do not mutate test code" stands — this touches the
SUT and is reverted. When you cannot run, name the mutation and the
assertion line that would catch it. No nameable failing mutation →
tautological (P0).
4. **Behaviour vs implementation** — a test that asserts internal structure,
a mock asserting on a mock, a snapshot of a fixture, or that would still
pass with the implementation deleted is implementation-shaped (P1); the
detectors in §2 do not catch this — read the assertions.
5. **e2e oracles against the source** — every selector, test id and text the
e2e waits on is rendered on the visited route under the named fixture
(`grep` the `data-testid`, read the render rule); an oracle that can never
match is `[scenario-defect]` (P0).
6. **Scope honesty** — the PLAN's `not covered` rows name each intent area
without a test and why; a silent gap is a P1 finding.
7. **You never ship** — no `git push`, `gh pr create`, `gh pr merge`; the
verdict returns to the run, whose deliver phase (or the human) opens the PR.
### Produced-test verdict (`PASS` is reachable here, unlike the audit in §5)
- **`PASS`** — all of: every `covered` claim verified at its `path:line`
(duty 1); every produced file executed with its recorded command and green,
and your re-run — when the harness is available — reproduces it (duty 2); no
`unverified` and no `failing` row among the produced files; ≥ 2 behaviours
mutated-or-reasoned with a nameable failing mutation each (duty 3); e2e
oracles verified (duty 5); `not covered` rows account for the intent
(duty 6); and the §2 detectors report P0 = P1 = P2 = 0 on the produced files.
- **`FAIL` with CONDITIONS** — no P0, but any P1/P2 (implementation-shaped
test, padded total, silent scope gap, style smells): list the fixes as
conditions above the verdict line. There is no conditional pass — the ledger
record (§5) may still say `CONDITIONAL`; the output line says `FAIL`.
- **`FAIL`** — any P0: `[unexecuted-test]`, a red produced test, an
unsupported `covered` claim, a tautological test, or an impossible oracle
`[scenario-defect]`.
Report block for this mode — the §8 `AUDIT:` line stays for the audit; a
message never carries two VERDICT lines and never quotes another one. The
verdict is one plain-text line, the LAST line of your output:
```
MODE: produced-test
REVIEWER: wicked-garden-qe-test-code-quality-auditor
RUN_ID: {RUN_ID}
VERDICT: PASS|FAIL
```
## Helper resolution (`{WT_LIB}`)
`{WT_LIB}` is the plugin's own qe helper directory — the helper modules ship
in-catalog (`scripts/qe/lib/`, ported from the retired wicked-testing package <!-- historical -->
in Phase 6c). Resolve it (cross-platform):
```bash
WT_LIB="$(wicked-garden path scripts/qe/lib)"
```