| name | co-evolution-audit |
| description | Flag production files that churn repeatedly while their paired test files do not change — the "Red Queen" co-evolution gap. Uses git log --stat to compute per-file change frequency over a configurable window, applies language-aware pairing heuristics (Python, JS/TS, Go, Java, C#), and produces a ranked table of stale-coverage pairs. Feeds test-health and test-improve as prioritization input, not a standalone gate. Use when you want to find high-churn files whose tests have gone stale, or before running /test-improve to identify the highest- leverage targets first. |
| argument-hint | [--since <date|N-days>] [--max-commits <N>] [--min-churn <N>] [--max-test-churn <N>] |
| user-invocable | true |
| effort | low |
Co-Evolution Audit
Role: worker. Reads git history, pairs production files to their test files,
and reports stale-coverage pairs. Does not edit any code or tests.
The Problem
A file that churns repeatedly while its tests never change is a high-risk
signal: the tests are likely passing for structural reasons (they still
compile and run) rather than because they verify current behavior. This
divergence is invisible to coverage tools — coverage stays green even as
the behavioral gap widens with every production change.
Parse Arguments
Arguments: $ARGUMENTS
| Flag | Default | Meaning |
|---|
--since <date|N-days> | 90 days ago | Lookback window. Accepts ISO date (2025-01-01) or relative (90 days ago). |
--max-commits <N> | 200 | Cap on commits scanned. The window is the earlier of --since and --max-commits. |
--min-churn <N> | 3 | Minimum production-file change count to flag a pair. |
--max-test-churn <N> | 1 | Maximum test-file change count for a pair to be flagged. A pair is flagged when production churn ≥ --min-churn AND test churn ≤ --max-test-churn. |
Graceful skip conditions
If any of the following is true, print a one-line notice and exit cleanly
(do not fail):
- The working directory has no
.git directory and is not inside a git
repository (git rev-parse --git-dir fails).
git log returns zero commits in the requested window.
- The repository is a shallow clone with fewer than 10 commits.
Step 1: Collect churn data
Run git log to collect per-file change frequency over the window:
git log --since="90 days ago" --max-count=200 --name-only \
--pretty=format:"" -- . | grep -v '^$' | sort | uniq -c | sort -rn
This produces a frequency table: how many commits touched each file.
Alternatively, --stat can be used for a combined view:
git log --since="90 days ago" --max-count=200 --stat \
--pretty=format:"COMMIT:%H"
Parse the output into a map: file_path → commit_count. Exclude merge
commits (--no-merges) to avoid inflating counts from merge-heavy workflows.
Practical command:
git log --since="90 days ago" --max-count=200 --no-merges \
--name-only --pretty=format:"" | grep -v '^$' | sort | uniq -c
Step 2: Classify files
Partition the file map into two groups:
- Production files — any file that is not in a test bucket (see Step 3
for the test-file heuristics). When in doubt, prefer to classify a file
as production rather than drop it.
- Test files — files matched by the pairing heuristics below.
Step 3: Pair production files to test files
Apply pairing heuristics in priority order. The first matching rule wins.
Python
| Production file | Candidate test files (checked in order) |
|---|
src/foo.py | tests/test_foo.py, tests/foo_test.py, test_foo.py |
pkg/bar/baz.py | pkg/bar/tests/test_baz.py, tests/test_baz.py, test_baz.py |
Rule: strip leading path prefix (src/, package dirs) and try:
tests/test_<stem>.py → <dir>/tests/test_<stem>.py → test_<stem>.py.
Test-file markers: path contains test or spec; filename starts with
test_ or ends with _test.py.
JavaScript / TypeScript
| Production file | Candidate test files (checked in order) |
|---|
src/foo.ts | src/foo.test.ts, src/__tests__/foo.test.ts, src/foo.spec.ts |
lib/bar.js | lib/bar.test.js, lib/__tests__/bar.test.js, lib/bar.spec.js |
Rule: same directory, add .test.<ext> or .spec.<ext> suffix; OR look
for a __tests__/ sibling directory with the same base name.
Test-file markers: filename ends with .test.ts, .test.js, .spec.ts,
.spec.js; path contains __tests__ or __spec__.
Go
| Production file | Candidate test files (checked in order) |
|---|
pkg/foo/bar.go | pkg/foo/bar_test.go |
cmd/app/main.go | cmd/app/main_test.go |
Rule: Go's convention is strict — test file is always <stem>_test.go in
the same directory. No other candidate is checked.
Test-file markers: filename ends with _test.go.
Java
| Production file | Candidate test files (checked in order) |
|---|
src/main/java/com/example/Foo.java | src/test/java/com/example/FooTest.java, src/test/java/com/example/FooSpec.java |
Rule: swap src/main/java → src/test/java, append Test or Spec to
the class name.
Test-file markers: path contains src/test; class name ends with Test
or Spec.
C# / .NET
| Production file | Candidate test files (checked in order) |
|---|
src/Foo/Bar.cs | tests/Foo.Tests/BarTests.cs, tests/Foo.Tests/BarTest.cs |
src/Project/Service.cs | tests/Project.Tests/ServiceTests.cs |
Rule: look for a sibling *.Tests or *.Test project directory; append
Tests or Test to the class stem.
Test-file markers: path contains .Tests/ or .Test/; class name ends
with Tests or Test.
Fallback (unknown language)
If no language-specific rule matches, apply the generic heuristic:
look for a file whose path contains test or spec and whose stem
contains the production file's stem (case-insensitive).
No test file found
If no candidate test file exists in the git history (it was never
committed), record test_churn = 0 and test_file = "(none found)".
These pairs have the highest co-evolution gap and should rank first.
Step 4: Compute co-evolution gap
For each production file with prod_churn >= --min-churn:
- Find its paired test file via Step 3.
- Look up
test_churn from the churn map (0 if no test file exists or
the test file had zero commits in the window).
- Flag the pair if
test_churn <= --max-test-churn.
Gap score (for ranking): prod_churn - test_churn. Higher score =
wider divergence.
Step 5: Report
Summary line
Co-Evolution Audit window: 90 days / 200 commits flagged: N of M pairs
Flagged pairs table
Sort by gap score descending (widest divergence first).
| Rank | Production file | Prod churn | Test file | Test churn | Gap |
|------|---------------------------|-----------|--------------------------------|-----------|-----|
| 1 | src/auth/session.py | 18 | tests/test_session.py | 0 | 18 |
| 2 | src/billing/invoice.py | 12 | tests/test_invoice.py | 1 | 11 |
| 3 | src/api/routes.ts | 9 | src/api/routes.test.ts | 0 | 9 |
| 4 | src/cache/store.go | 7 | (none found) | 0 | 7 |
Clean pairs (informational, collapsed)
Files that passed the churn threshold but whose tests co-evolved (test
churn > --max-test-churn) are listed in a collapsed section as evidence
that the pairing heuristic is working, not a concern.
No findings
If no pairs are flagged, print:
No co-evolution gaps found in the audit window. All high-churn production
files have co-evolving test files, or no file exceeded --min-churn (3).
Step 6: Remediation guidance
After the table, print a prioritized action list:
Immediate priorities (gap ≥ 5 or test_file = "none found")
These are the highest-risk files: they changed frequently but tests did
not follow. Recommended next steps:
-
Run /test-improve on the flagged file — the test-improve skill
will assess the existing tests for the file and propose targeted
improvements. Pass the flagged production file path as the scope.
Example: /test-improve --scope src/auth/session.py
-
Run /mutation-testing on the flagged file — stale tests often
have missing assertions that mutation testing will surface. Run after
/test-improve has added structural coverage.
Example: /mutation-testing --scope src/auth/session.py
-
Check for a missing test file — if test_file = "(none found)",
the production file has never had a dedicated test file. Creating one
is higher leverage than improving an existing sparse test file.
Secondary priorities (gap 2–4)
These pairs have some test co-evolution but lag behind production churn.
Feed them to /test-health as context: these files are candidates for
the suite's coverage improvement roadmap, but they are not urgent fire
drills.
Integration with other skills
| Signal | Where to send it |
|---|
gap ≥ 5 or test_file = "(none found)" | /test-improve (immediate) |
gap 2–4 | /test-health (roadmap input) |
| Any flagged file | /mutation-testing (after coverage is added) |
| Flagged file also has review findings | Treat as compounding risk — prioritize above clean flagged files |
Constraints
- Advisory only. Do not edit any files.
- No git rewrite. Never run any git command that modifies history
(
reset, rebase, cherry-pick, push). Read-only git commands only.
- Graceful skip over binary and generated files. Skip files matching
common generated-file patterns (
*.pb.go, *.generated.cs,
migrations/*.sql, dist/*, vendor/*, node_modules/*). These are
not hand-authored code and their churn inflates the signal.
- Shallow clones. If the repo is shallow (fewer than 10 commits
visible), note it in the summary and advise the operator to run on a
full clone for a meaningful signal.
- Not a gate. Co-evolution audit is a prioritization input, not a
pass/fail quality gate. Do not block CI or a PR on its output.