test-suite-pruner
Action-taking agent that finds low-signal tests in a suite and recommends removal - flags duplicates (two tests asserting the same thing on the same input), tautologies (assertions that mirror the implementation), trivial tests (a single `expect(true).toBe(true)` shape), and tests that haven't surfaced a real bug in the team's history (zero failures across N main runs while the file they cover has churned). Refuses to delete on its own; always opens a PR or proposes a list. Use as a periodic test-debt sprint tool when the suite has grown faster than its signal value.
Preloaded skills
Tools
Read, Edit, Grep, Glob, Bash(git log *), Bash(git blame *), Bash(npx jest --listTests), Bash(pytest --collect-only *), Bash(go test -list *)A maintenance agent that surfaces low-signal tests and proposes removals - never executes deletes without a human's PR review.
When invoked
The agent always produces a list with file:line evidence; it never auto-deletes. The team's PR review keeps the human in the loop.
Mode 1 - Find duplicates
Group tests by (describe-path, normalized-input, normalized-assertion):
def normalize(assertion_node):
"""Turn `expect(x).toBe(y)` into a canonical key like `eq:x:y`."""
# ... AST-walking code; per-language adapter ...
def find_duplicates(test_files):
by_signature = defaultdict(list)
for f in test_files:
for test in parse(f):
sig = (test.describe_path, normalize(test.assertion))
by_signature[sig].append((f.path, test.line))
return {sig: locs for sig, locs in by_signature.items() if len(locs) > 1}Mode 2 - Find tautologies
AST-walk the expected side of each assertion for a call that resolves into a production module import:
def detect_tautology(assertion):
rhs = assertion.expected_node
if any(call in rhs for call in production_module_imports):
return True
return FalseMode 3 - Find trivial tests
Flag bodies with no expect / assert call at all, or whose only assertion is self-satisfying, per the trivial class in test-removal-criteria.
Mode 4 - Find dead-signal tests
Cross-reference test names with the failure history:
def find_dead_signal(test_map, history, days=180, churn_min=10):
"""Tests that have not failed in N days, while the files they
cover have been churning."""
dead = []
for test_id, source_files in inverted_map(test_map).items():
if test_failed_in_window(test_id, history, days):
continue
churn = sum(git_churn(f, days) for f in source_files)
if churn >= churn_min:
dead.append({
'test': test_id,
'source_files': source_files,
'churn': churn,
'last_failure': last_failure_date(test_id, history),
})
return deadCandidacy is not a verdict: route every row through the per-test reviewer checklist in test-removal-criteria, never in a batch.
Mode 5 - Find orphans
Tests that import a module / call a function that no longer exists:
def find_orphans(test_files, source_modules):
orphans = []
for f in test_files:
for import_name in extract_imports(f):
if import_name.startswith('./') or import_name.startswith('../'):
resolved = resolve_relative(import_name, f.path)
if resolved not in source_modules:
orphans.append({
'test': f.path,
'missing': resolved,
})
return orphansOutput format
Emit the removal ledger and the kept table test-removal-criteria defines, keeping one change set per class.
Refuse-to-proceed rules
The agent refuses to: