agentleFS
greglas75greglas75/zuvo/skills/tests-performance/SKILL.md

tests-performance

Skill7 starsChanged 27 days agoMITView on GitHub

Test suite performance audit and optimization. Measures baseline timing, audits runner configuration against TP1-TP17 checklist, identifies the slowest tests, and produces an impact-ranked action plan. Modes: full audit (default), baseline (measure only), verify (compare to saved baseline), --no-run (config audit only), --path <dir> (monorepo scope).

The file

Show its rules and text

We split this skill into the rules it gives an agent

22 rules. About 0 are common, 9 are distinctive, and 13 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈9

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 13

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Scope: Test suite speed and runner configuration.

    line 29
  2. Very specific:

    Out of scope: Test quality (use zuvo:test-audit), flaky/intermittent test investigation (use zuvo:debug — it owns reproduction and bisect.

    line 30
  3. Distinctive:

    Measure before and after: No measurement means no improvement claim.

    line 39
  4. Very specific:

    Read ../../shared/includes/env-compat.md for progress tracking and user interaction patterns.

    line 72
  5. Very specific:

    Read ../../shared/includes/run-logger.md for log format and file path resolution.

    line 76
  6. Very specific:

    Read ../../shared/includes/codesift-setup.md for initialization.

    line 81
  7. Very specific:

    If --path <dir> provided, scope detection to that directory only

    line 102
  8. Very specific:

    Check the directory containing the nearest package.json to cwd

    line 103
  9. Distinctive:

    If multiple runners at the same depth, list them and ask which to audit

    line 105
  10. Very specific:

    If multiple configs found, warn: WARNING: N runner configs found.

    line 122
  11. Distinctive:

    Read the detected config file fully.

    line 126
  12. Distinctive:

    Run the full test suite 3 times.

    line 136
  13. Very specific:

    Slow suite escape hatch: If the first run exceeds 60 seconds, run exactly 2 total (1 warmup + 1 measured).

    line 138
  14. Distinctive:

    If stddev exceeds 15% of median, mark as UNSTABLE with warning.

    line 163
  15. Distinctive:

    Read the runner config file.

    line 175
  16. Distinctive:

    Read the test file

    line 215
  17. Very specific:

    Always pass an explicit search root and exclude dependency/build trees — a bare grep -r … --include="*.test.*" walks node_modules, whose …

    line 232
  18. Very specific:

    If the current time is slower than baseline, warn: REGRESSION: Suite is X% slower than baseline.

    line 317
  19. Very specific:

    Follow the retrospective protocol from retrospective.md.

    line 339
  20. Distinctive:

    If gate check skips: print "RETRO: skipped (trivial session)" and proceed.

    line 341
  21. Very specific:

    Append via wrapper (REQUIRED): Never >> directly to ~/.zuvo/runs.log — the wrapper is the gate that verifies a retro entry exists for this …

    line 343
  22. Very specific:

    All 13 fields are mandatory (run-logger.md: "tab count must be exactly 12").

    line 357

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

zuvo:tests-performance — Test Suite Performance Audit

Measurement-driven optimization of the test suite. Establishes a baseline, audits the runner configuration, identifies slow tests, and ranks fixes by expected impact.

Scope: Test suite speed and runner configuration. Reducing wall-clock time of the test suite. Out of scope: Test quality (use zuvo:test-audit), flaky/intermittent test investigation (use zuvo:debug — it owns reproduction and bisect; zuvo:fix-tests repairs known anti-patterns, it does not diagnose nondeterminism), CI pipeline optimization (use zuvo:ci-audit), systematic test anti-patterns (use zuvo:fix-tests).

This skill does not write. It measures, scores and proposes exact config diffs; applying them is the user's call. Phase 5 re-measures after they have been applied.

Core Principles

  1. Measure before and after. No measurement means no improvement claim.
  2. Config changes before code changes. A 5-minute config fix often outperforms a 2-hour test rewrite.
  3. Rank by impact, not effort. Present changes in expected-speedup order.
  4. Runner-specific, not generic. Jest advice for Vitest projects causes harm.
  5. Recommended values are hypotheses. Impact ranges are starting points -- always verify with before/after measurement.

Argument Parsing

ArgumentBehavior
(empty)Full audit: baseline + config audit + slow scan + action plan
baselinePhase 1 only: measure and save baseline
verifyPhase 5 only: re-measure and compare to saved baseline
--no-runSkip test execution: Phase 2 (config audit), Phase 3.2 (pattern scan) and Phase 4, with no Phase 3.1 — per-file slow-test ranking reads the baseline JSON that only Phase 1 produces. Report the action plan without measured impact, and print TP Score with baseline: none (--no-run)
--path <dir>Scope runner detection and test execution to a specific directory

Mandatory File Loading

PHASE 0 — Bootstrap (before measuring anything)

  1. ../../shared/includes/codesift-setup.md      -- [READ | MISSING -> WARN]
  2. ../../shared/includes/env-compat.md          -- [READ | MISSING -> WARN]

retrospective.md and run-logger.md are END-of-run includes — they are loaded at completion, not here.

Environment Compatibility

Read ../../shared/includes/env-compat.md for progress tracking and user interaction patterns.

Run Logging

Read ../../shared/includes/run-logger.md for log format and file path resolution. Read ../../shared/includes/retrospective.md for log format and file path resolution.

CodeSift Integration

Read ../../shared/includes/codesift-setup.md for initialization.

Key tools for this skill:

PhaseTaskCodeSift toolFallback
3Test file inventoryget_file_tree(repo, name_pattern="*.test.*")find + wc -l
3Test structure per fileget_file_outline(repo, file_path)Read the file
3Batch-read setup blocksget_symbols(repo, symbol_ids=[...])Multiple Read calls
3Detect slow patterns`search_text(repo, query="setTimeoutsleepwaitFor", regex=true, file_pattern=".test.")`Grep
3Heavy setup detection`search_text(repo, query="beforeEach.*prismabeforeAll.seed", regex=true, file_pattern=".test.*")`Grep
3Trace expensive setup callstrace_call_chain(repo, symbol_name=<setup_fn>, direction="callees", depth=2)Skip

Phase 0: Detect Runner

One runner per audit. If the project uses multiple runners, run this skill once per runner.

Detection Priority

  1. If --path <dir> provided, scope detection to that directory only
  2. Check the directory containing the nearest package.json to cwd
  3. Fall back to nearest config file to the search root
  4. If multiple runners at the same depth, list them and ask which to audit

Runner Signals

SignalRunner
vitest.config.* or vitest.workspace.*Vitest
jest.config.* or jest key in package.jsonJest
pytest.ini, setup.cfg, or [tool.pytest] in pyproject.tomlpytest
# Detection script (scope-aware)
SEARCH_ROOT="${path_arg:-.}"
find "$SEARCH_ROOT" -maxdepth 5 -not -path "*/node_modules/*" -not -path "*/.git/*" \
  \( -name "vitest.config.*" -o -name "jest.config.*" -o -name "pytest.ini" \) 2>/dev/null

If multiple configs found, warn: WARNING: N runner configs found. Use --path <dir> to target a specific package.

Read Config File

Read the detected config file fully. Extract current values for all TP items. Record which settings are explicitly set versus defaulted.


Phase 1: Baseline Measurement

Skip if --no-run was specified.

Run Suite (3x Normal, 2x for Slow Suites)

Run the full test suite 3 times. The first run warms caches; use the median of all 3.

Slow suite escape hatch: If the first run exceeds 60 seconds, run exactly 2 total (1 warmup + 1 measured). Mark baseline as runs: 2. Never accept a single run.

RunnerCommandPer-file timing
Vitestrt --light npx vitest run --reporter=jsonJSON includes per-file duration
Jestrt --light npx jest --jsonJSON includes testResults[].perfStats
pytestpython -m pytest --durations=0 -q--durations=0 lists all test times

Redirect stderr to memory/tests-performance.stderr.log for diagnostic review. Create memory/ first if needed.

Baseline Summary

BASELINE
-----
  Median total time: X.Xs (min: X.Xs, max: X.Xs, stddev: X.Xs)
  Stability: STABLE | UNSTABLE (stddev > 15% of median)
  Test count: N
  Per-test avg: X.Xms
  Top 5 slowest files:
    1. path/to/slow.test.ts -- X.Xs (N tests)
    2. ...
-----

If stddev exceeds 15% of median, mark as UNSTABLE with warning.

Save Baseline

Save to memory/tests-performance-baseline.<runner>.json in the project root. If memory/ does not exist, create it.

If argument is baseline: stop here. Print baseline summary and exit.


Phase 2: Config Audit (TP1-TP17)

Read the runner config file. Score each TP item as 1 (optimal), 0 (suboptimal), or N/A.

TP Checklist

TPItemWhat optimal looks like
TP1Transform efficiencyJest: @swc/jest or esbuild-jest. Vitest: native (always 1). pytest: N/A.
TP2Default environmentnode globally, jsdom only per-file where needed
TP3DOM engine (Vitest only)happy-dom instead of jsdom for DOM tests
TP4Worker/concurrency strategyJest: maxWorkers 50%. Vitest: pool threads + maxConcurrency 20. pytest: xdist + -n auto.
TP5Test isolation (Vitest only)isolate: false if tests are side-effect-free
TP6Test timeoutEffective timeout at or below 10 seconds
TP7Suite splitSeparate configs or markers for unit/integration/e2e
TP8Coverage thresholds [GOV]Thresholds set (governance, not speed)
TP9Cache enabledCache enabled and persisted across runs
TP10Fail-fast [CI]bail: 1 in CI only (economics, not speed)
TP11Collection scopetestMatch or testpaths restricted to source directories
TP12Dependency pre-bundlingVitest: deps.optimizer enabled. Jest: transformIgnorePatterns tuned.
TP13Module path aliasesPath aliases consistent between app and test configs
TP14Coverage toolv8 (fast) over istanbul (accurate but slower)
TP15Console noiseNo uncontrolled console.log in tests (spy + suppress)
TP16Memory degradationNo memory leaks across runs (stable heap after 3 runs)
TP17Type-check separationType checking runs separately from test execution

Scoring Rules

  • TP8 [GOV] and TP10 [CI] are governance/economics items, NOT performance items. Score them in the report but exclude from the weighted performance score and action plan impact estimates.
  • TP5 (isolate: false) has HIGH risk. Score as N/A if uncertain about side effects. Never recommend without a guard-rail protocol.
  • For each scored item, record: current value, optimal value, expected impact range, and required change.

Phase 3: Slow Test Scan

Identify the slowest tests and classify why they are slow.

3.1 Per-File Analysis

From baseline JSON data, extract the top 10 slowest files. For each:

  1. Read the test file
  2. Count tests and compute per-test average
  3. Classify the slow cause:
ClassificationSignalTypical fix
SETUP_HEAVYbeforeEach with DB seed, API calls, heavy object constructionShared fixtures, lighter setup
REAL_TIMERSsetTimeout, setInterval, Date.now without fakesvi.useFakeTimers() or jest.useFakeTimers()
DOM_HEAVYMultiple render() calls per describe blockSingle render with targeted assertions
LARGE_FIXTURESInline fixture objects exceeding 50 linesExtract to factory function
SEQUENTIAL_ASYNCMultiple sequential await calls that could parallelizePromise.all where safe
RECOMPUTATIONSame expensive computation repeated across testsbeforeAll with cached result

3.2 Pattern Scan

Scan all test files for slow patterns:

Always pass an explicit search root and exclude dependency/build trees — a bare grep -r … --include="*.test.*" walks node_modules, whose vendored specs outnumber the project's own and turn every count into noise.

SCAN_ROOT="${path_arg:-.}"
EXCL=(--exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist --exclude-dir=build
      --exclude-dir=coverage --exclude-dir=.next --exclude-dir=vendor --exclude-dir=.venv)

# Real timers in test files
grep -rn "${EXCL[@]}" --include="*.test.*" "setTimeout\|sleep\|waitFor" "$SCAN_ROOT" | wc -l

# Heavy setup
grep -rn "${EXCL[@]}" --include="*.test.*" "beforeEach.*prisma\|beforeAll.*seed\|beforeEach.*render" "$SCAN_ROOT" | wc -l

# Explicit waits
grep -rn "${EXCL[@]}" --include="*.test.*" "waitForTimeout\|sleep(" "$SCAN_ROOT" | wc -l

Phase 4: Action Plan

Rank all findings by expected impact. Separate config changes from code changes.

Impact Ranking

ACTION PLAN (ranked by expected impact)
-----
  #  Type    TP   Change                          Impact
  1  config  TP1  Switch to @swc/jest              40-70% faster transforms
  2  config  TP4  Set pool: 'threads'              10-30% total suite
  3  config  TP12 Enable deps.optimizer            30-50% collection phase
  4  config  TP2  Set env: 'node' globally         20-40% on non-DOM files
  5  code    --   Extract shared setup in auth/    ~3s saved (top-5 slow file)
  ...

  Excluded from ranking (governance/economics):
    TP8  Coverage thresholds: not set (no speed impact)
    TP10 Fail-fast: not set (only helps on red suites)
-----

Rollout Sequence

For config changes, propose a safe rollout order:

  1. Changes with zero risk of breaking tests (TP1, TP9, TP11, TP12)
  2. Changes that need verification (TP2, TP4, TP7)
  3. Changes with side-effect risk (TP5 -- requires isolation audit first)

For each change, provide the exact config diff:

TP4 (worker strategy):
  Current:  pool: 'forks' (default)
  Proposed: pool: 'threads', poolOptions: { threads: { maxThreads: 8 } }
  File:     vitest.config.ts
  Risk:     Low (threads is default in Vitest 2.x+)

Phase 5: Verify

Only runs when argument is verify or after applying changes from Phase 4.

Re-run the test suite with the same protocol as Phase 1 (3 runs, median). Load the saved baseline from memory/tests-performance-baseline.<runner>.json.

Compare:

VERIFICATION
-----
  Baseline:  X.Xs (N tests)
  Current:   Y.Ys (N tests)
  Delta:     -Z.Zs (P% faster)
  Stability: [STABLE | UNSTABLE]

  Per-change impact (if individual changes were applied):
    TP1 (@swc/jest):     X.Xs -> Y.Ys (-Z%)
    TP4 (threads):       X.Xs -> Y.Ys (-Z%)
-----

If the current time is slower than baseline, warn: REGRESSION: Suite is X% slower than baseline. Revert recent changes and investigate.


Completion Report

TESTS-PERFORMANCE COMPLETE
-----
  Runner:      [runner] ([config path])
  Baseline:    X.Xs (N tests)
  TP Score:    [N]/15 performance items optimal ([M] suboptimal, [K] N/A) | governance: TP8 [set|not set], TP10 [set|not set]
  Slow tests:  [N] files classified
  Action plan: [N] items ([M] config, [K] code)
  Top impact:  [description of #1 change] -> expected [P]% improvement
  Run: <ISO-8601-Z>	tests-performance	<project>	-	<Q>	<VERDICT>	<TASKS>	<DURATION>	<NOTES>	<BRANCH>	<SHA7>	<INCLUDES>	<TIER>
-----

Retrospective (REQUIRED)

Follow the retrospective protocol from retrospective.md. Gate check → structured questions → TSV emit → markdown append. If gate check skips: print "RETRO: skipped (trivial session)" and proceed.

Append via wrapper (REQUIRED). Never >> directly to ~/.zuvo/runs.log — the wrapper is the gate that verifies a retro entry exists for this run. Order: retro bash executed → wrapper invoked → completion claimed.

printf '%b\n' "$RUN_LINE" | ~/.zuvo/append-runlog

Expected stdout: OK: appended to runs.log (retro verified for <skill> on <project>). If exit 2 with RETRO_REQUIRED — go execute the retro bash from retrospective.md first; never bypass with ZUVO_SKIP_RETRO_GATE=1. After the wrapper succeeds, print a Logs: evidence line (tail -1 ~/.zuvo/retros.log, grep -c "^<!-- RETRO -->" ~/.zuvo/retros.md, tail -1 ~/.zuvo/runs.log) before claiming completion. Printing the markdown retro section without executing the bash leaves all three log files empty.

<DURATION>: use full (full audit) or baseline (baseline-only run). <Q>: performance TP score as N/15 (TP8 and TP10 are excluded — see Scoring Rules), or - for baseline-only. <TASKS>: number of action plan items. <INCLUDES>: the include list per run-logger.md field 12, or -. <TIER>: - — this skill does not tier.

All 13 fields are mandatory (run-logger.md: "tab count must be exactly 12"); write - for anything with no value rather than dropping the field.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.