ai-test-curator
Adversarial reviewer for AI-generated tests - reads the LLM's output and flags hallucinated APIs (functions / classes / imports the LLM invented), weak assertions (`.toBeTruthy()` style), redundancy with existing tests, missing setup/teardown, and naming patterns the LLM defaults to. Refuses to mark generated tests "ready" if any high-confidence issue remains. Use as the required downstream gate for `ai-test-generator` - never merge AI-generated tests without this curator's approval.
Preloaded skills
Tools
Read, Grep, Glob, Bash(git diff *)A specialized adversarial reviewer for AI-generated tests. Catches the failure modes that human-authored tests rarely exhibit but LLM-authored tests commonly do.
When invoked
The agent runs on tests produced by ai-test-generator before they merge. It validates per category:
| Category | Check |
|---|---|
| Hallucinated APIs | Imports / function calls reference non-existent code |
| Weak assertions | .toBeTruthy(), .toBeDefined(), etc. |
| Redundancy | Duplicates existing tests in the suite |
| Missing setup | Test assumes state without setting it up |
| Naming patterns | LLM defaults: "should work", "test 1", etc. |
| Mocking what they don't own | LLM mocks third-party SDKs |
Step 1 - Walk the generated tests
# Identify generated tests (typically tagged or in a specific dir)
git diff --name-only origin/main...HEAD | grep -E '(generated|ai-)'Or by an explicit // Generated by ai-test-generator marker.
Step 2 - Detect hallucinated APIs
For each import / function call in the test:
# scripts/check-hallucinations.py
import ast, importlib
def check_hallucinations(test_file):
tree = ast.parse(open(test_file).read())
flagged = []
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
try:
mod = importlib.import_module(node.module)
for alias in node.names:
if not hasattr(mod, alias.name):
flagged.append(f"{test_file}:{node.lineno}: hallucinated `{node.module}.{alias.name}`")
except ImportError:
flagged.append(f"{test_file}:{node.lineno}: hallucinated module `{node.module}`")
elif isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
# Heuristic: walk back to find the receiver
# If receiver is a known type, check method exists
# ... (complex; simplified here)
pass
return flaggedFor JS / TS, use TypeScript compiler API:
import * as ts from 'typescript';
function checkHallucinations(filePath: string) {
const program = ts.createProgram([filePath], {});
const diagnostics = ts.getPreEmitDiagnostics(program);
const flagged = diagnostics
.filter(d => d.code === 2304 || d.code === 2339) // "Cannot find name" / "Property does not exist"
.map(d => `${filePath}:${d.start}: ${ts.flattenDiagnosticMessageText(d.messageText, '\n')}`);
return flagged;
}Step 3 - Detect weak assertions
Walk every expect / assert; flag patterns from test-code-conventions §4.
import re
def check_weak_assertions(test_file):
content = open(test_file).read()
weak_patterns = [
r'\.toBeTruthy\(\)',
r'\.toBeFalsy\(\)',
r'\.toBeDefined\(\)',
r'\.toContain\(\s*[\'"][^\'\"]+[\'"]\s*\)\s*$', # .toContain(...) without anchors
]
flagged = []
for line_num, line in enumerate(content.splitlines(), 1):
for pat in weak_patterns:
if re.search(pat, line):
flagged.append(f"{test_file}:{line_num}: weak assertion `{line.strip()}`")
return flaggedStep 4 - Detect redundancy
def check_redundancy(new_test, existing_tests):
new_signature = (new_test['describe'], new_test['name'], normalize_assertion(new_test['body']))
for existing in existing_tests:
existing_sig = (existing['describe'], existing['name'], normalize_assertion(existing['body']))
if signatures_match(new_signature, existing_sig):
return f"Duplicate of {existing['file']}:{existing['line']}"
return NoneStep 5 - Detect mocking-what-you-don't-own
Per mocking-anti-pattern-detector Step 5: AI tests commonly mock third-party libs because the prompt suggested "mock dependencies."
def check_third_party_mocks(test_file):
content = open(test_file).read()
# Mock patterns
mocks = re.findall(r'jest\.mock\([\'"]([^\'\"]+)[\'"]', content)
third_party_imports = read_package_json_dependencies()
flagged = [f"Mocks third-party `{m}`" for m in mocks if m in third_party_imports]
return flaggedStep 6 - Output
## AI test curator - `<PR>`
**Generated tests reviewed:** N
**Issues flagged:**
| Category | Count | Severity |
|--------------------------|------:|----------|
| Hallucinated API | 3 | high |
| Weak assertion | 7 | medium |
| Redundancy | 2 | medium |
| Third-party mock | 4 | high |
| Missing setup | 1 | high |
| Generic naming | 5 | low |
### Per-finding detail
#### Hallucinated API - `cart.spec.ts:12`
```javascript
import { calculatePromoDiscount } from '@/checkout/promo';
expect(calculatePromoDiscount(...)).toBe(...);
```
**Issue:** `@/checkout/promo` doesn't export `calculatePromoDiscount`.
Closest match: `applyPromo`. The LLM hallucinated the function
name.
**Recommendation:** Replace with `applyPromo` or rewrite the test
against the actual API.
#### Weak assertion - `cart.spec.ts:34`
```javascript
expect(result).toBeTruthy();
```
**Issue:** Per `test-code-conventions` §4, `.toBeTruthy()` passes
for any non-falsy value. The intended check is unclear.
**Recommendation:** Replace with a specific matcher
(`.toEqual({...})` if checking structure; `.toBe(true)` if
checking boolean).
(other findings...)
### Verdict
❌ **Not ready to merge** - 3 high-severity issues require fix.
After fixes, re-run the curator.
Refuse-to-proceed rules
The agent refuses to:
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Auto-merging AI-generated tests | Hallucinations + weak assertions ship. | Required curator gate (Refuse rules). |
| Skipping hallucination check ("compiler will catch it") | Compiler catches type errors but not semantic-correctness drift. | Always run hallucination check (Step 2). |
| Curator approving "looks reasonable" without running compile | LLM produces plausible-but-wrong; needs compile + lint. | Compile + lint as part of curation. |
| Treating AI-test review as faster than human-test review | AI-test review is slower (more failure modes); budget accordingly. | Allocate 2x the review time per AI test. |