Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generates and self-executes a diff-derived test plan for a PR. Use when validating PR changes before merge. Do not use for code review; use sanctum:pr-review.
.claude/skills/athola-validate-pr/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 64% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 72% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 128% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 197% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 197% | 0% |
Generate and self-execute a validation plan matched to what actually changed in a PR. Replaces generic "tests pass" with area-targeted evidence and revert-test quality checks that prove tests catch regressions.
/fix-pr Step 5 (Validate), before Step 6 (Complete)--scope minor with only formatting or doc changes (no logic changed)--skip-validate passed to /fix-prfetch diff -> group by area -> generate steps -> execute -> revert-test -> tablebash# Get changed file list from the PR PR_NUMBER=<number from invocation or current branch> CHANGED=$(gh pr diff "$PR_NUMBER" --name-only) # Fallback when no PR number: # CHANGED=$(git diff "origin/$(git rev-parse --abbrev-ref HEAD@{upstream})...HEAD" \ # --name-only 2>/dev/null)
Group changed files into areas using ripgrep (grep if rg unavailable):
bashRUST_FILES=$(echo "$CHANGED" | rg '\.rs$|Cargo\.(toml|lock)$' || true) PY_FILES=$(echo "$CHANGED" | rg '\.py$|pyproject\.toml$|requirements.*\.txt$' || true) SH_FILES=$(echo "$CHANGED" | rg '\.sh$|\.githooks' || true) GRAMMAR_FILES=$(echo "$CHANGED" | rg '\.(lark|peg|g4)$' || true)
Area routing table:
| Area | File patterns | Verification type | |------|---------------|-------------------| | Rust | *.rs, Cargo.toml, Cargo.lock | cargo build + per-crate test | | Python | *.py, pyproject.toml | pytest per changed module | | Shell | *.sh, .githooks/* | shellcheck | | Grammar | *.lark, *.peg, *.g4 | language-specific lint | | Build/config | *.yaml, *.json, *.toml | parse check |
For each non-empty area, generate and run at least one verification step. Assign [E1], [E2], ... labels to each captured output.
bash# Build with default features cargo build --workspace 2>&1 # Evidence: [En] → "0 errors, 0 warnings" # Build with --all-features cargo build --workspace --all-features 2>&1 # Evidence: [En+1] # Per-crate test for each changed crate # Extract crate directory from changed path, e.g. crates/token-types/src/lib.rs CHANGED_CRATES=$(echo "$RUST_FILES" \ | rg -o '(?:crates|src)/[^/]+' \ | sort -u \ | xargs -I{} basename {}) for CRATE in $CHANGED_CRATES; do cargo test -p "$CRATE" 2>&1 done
bash# Targeted test per changed module for PY_FILE in $PY_FILES; do MODULE=$(basename "${PY_FILE%.py}") TEST_FILE="tests/test_${MODULE}.py" if [[ -f "$TEST_FILE" ]]; then uv run pytest "$TEST_FILE" -v 2>&1 fi done # Or project-specific runner if Makefile target exists make test 2>&1 || uv run pytest tests/ -v 2>&1
bashfor SH_FILE in $SH_FILES; do [[ -f "$SH_FILE" ]] && shellcheck "$SH_FILE" 2>&1 done
bash# YAML files for YML in $(echo "$CHANGED" | rg '\.ya?ml$' || true); do [[ -f "$YML" ]] && python3 -c "import yaml; yaml.safe_load(open('$YML'))" \ && echo "PASS: $YML" || echo "FAIL: $YML" done # JSON files for JSON_F in $(echo "$CHANGED" | rg '\.json$' || true); do [[ -f "$JSON_F" ]] && python3 -m json.tool "$JSON_F" > /dev/null \ && echo "PASS: $JSON_F" || echo "FAIL: $JSON_F" done
Prove at least one test is a genuine guard, not a dead assertion.
Safety: abort if the working tree has uncommitted changes.
bashif ! git diff --exit-code > /dev/null 2>&1; then echo "[RT] SKIP: working tree dirty: revert-test unsafe" # Mark INCONCLUSIVE and continue fi
Algorithm (one representative fix):
#[test] in the same crate that exercises a changed function.tests/test_<module>.py for a changed <module>.py.1specifically. A pytest usage error (4) or an empty collection (5) is also non-zero, so a harness that only checks "not zero" reports a dead assertion as a genuine guard.
git checkout -- <file> (git-based restore, safe on interrupt).Revert-test output format:
[RT-1] Target: <file>:<line>: <description of fix>
[RT-2] Broke fix: <edit description>
[RT-3] Ran: <test command> → <test name> FAILED (expected)
[RT-4] Restored: git checkout -- <file>
[RT-5] Ran: <test command> → <test name> PASSED
Result: PASS: test is a genuine guardReverting a test that guards document content:
Content tests assert on prose, and this repo wraps prose at 80 columns, so any anchor phrase long enough to be meaningful eventually straddles a line break. Collapse whitespace before matching. Otherwise a pure reflow turns the test red and tempts an author to "fix" it by unwrapping the line.
Normalizing reintroduces the hazard the revert test exists to catch: a rejoined anchor can also appear elsewhere in the file, so deleting the paragraph the test guards leaves it green. Anchor on a full clause that is unique to that paragraph, then delete the paragraph and confirm the test goes red. A DDD paradigm test passed its revert check this way in PR #612 while guarding nothing.
When no covering test exists:
Revert-test: INCONCLUSIVE: no covering test for <changed area>
Recommendation: add a test for <changed function or behaviour>After all area checks and the revert-test:
bash# Rust workspace cargo test --workspace 2>&1 # Python project uv run pytest tests/ -v 2>&1 # Mixed project: run both cargo test --workspace 2>&1 && uv run pytest tests/ -v 2>&1
Capture full output as final evidence [En].
markdown### validate-pr: <PR title or number> | Area | Step | Evidence | Result | |------|------|----------|--------| | Rust: token-types | cargo build --workspace | [E1] 0 errors | PASS | | Rust: token-types | cargo test -p token-types | [E2] 12 passed | PASS | | Rust: token-types | cargo build --all-features | [E3] 0 errors | PASS | | Shell: hooks/pre-commit | shellcheck | [E4] 0 issues | PASS | | Revert-test: lib.rs:45 | break/fail/restore | [RT-1..5] genuine guard | PASS | | Final: cargo test --workspace | full suite | [E5] 694 passed, 0 failed | PASS | **Totals**: 6 steps: 6 PASS, 0 FAIL, 0 INCONCLUSIVE
When --post is given, post the summary table as a PR comment:
bashgh pr comment "$PR_NUMBER" --body "$(cat /tmp/validate-pr-summary.md)"
Skip posting when invoked from /fix-pr: results feed into the Gate 3 summary comment instead.
When any step produces FAIL:
/fix-pr: halt before Step 6 (Complete). The user mustfix the failures or pass --skip-validate to /fix-pr to bypass.
INCONCLUSIVE results are reported but do not halt the workflow.
gh pr diff --name-only returned a non-empty file list (diff fetched)[E1], [E2], etc.) with theactual command output, not fabricated
documented as INCONCLUSIVE with reason
/fix-pr before Step 6 when called from fix-prconfirmed successful for any revert-test mutation)
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | fail→pass | 13,344 | 5,674 | -57% | 1 | 1 | 0% | 2,071 | 3,395 | +64% | 0 | 0 | — |
case-01 | fail→fail | 6,363 | 6,566 | +3% | 1 | 1 | 0% | 307 | 2,734 | +791% | 0 | 0 | — |
case-02 | fail→fail | 31,476 | 7,308 | -77% | 1 | 1 | 0% | 5,662 | 2,989 | -47% | 0 | 0 | — |
case-03 | fail→fail | 7,175 | 5,550 | -23% | 1 | 1 | 0% | 248 | 2,822 | +1038% | 0 | 0 | — |
case-05 | pass→pass | 11,258 | 6,333 | -44% | 1 | 1 | 0% | 1,718 | 3,392 | +97% | 0 | 0 | — |
case-06 | fail→pass | 11,052 | 5,054 | -54% | 1 | 1 | 0% | 1,836 | 3,158 | +72% | 0 | 0 | — |
case-07 | fail→pass | 7,268 | 2,430 | -67% | 1 | 1 | 0% | 1,202 | 2,744 | +128% | 0 | 0 | — |
case-08 | pass→pass | 9,932 | 3,026 | -70% | 1 | 1 | 0% | 1,564 | 2,808 | +80% | 0 | 0 | — |
case-09 | pass→pass | 4,876 | 5,626 | +15% | 1 | 1 | 0% | 689 | 2,994 | +335% | 0 | 0 | — |
case-10 | pass→pass | 12,423 | 5,487 | -56% | 1 | 1 | 0% | 2,267 | 3,329 | +47% | 0 | 0 | — |
case-11 | fail→pass | 6,104 | 2,947 | -52% | 1 | 1 | 0% | 944 | 2,800 | +197% | 0 | 0 | — |
case-12 | pass→pass | 8,807 | 4,128 | -53% | 1 | 1 | 0% | 1,229 | 3,008 | +145% | 0 | 0 | — |
case-13 | fail→fail | 14,603 | 3,029 | -79% | 1 | 1 | 0% | 1,953 | 2,857 | +46% | 0 | 0 | — |
case-14 | fail→pass | 6,842 | 4,305 | -37% | 1 | 1 | 0% | 1,037 | 3,077 | +197% | 0 | 0 | — |
case-15 | fail→pass | 8,782 | 3,204 | -64% | 1 | 1 | 0% | 1,338 | 2,802 | +109% | 0 | 0 | — |
case-16 | pass→pass | 10,280 | 6,443 | -37% | 1 | 1 | 0% | 1,591 | 3,398 | +114% | 0 | 0 | — |
case-17 | fail→pass | 12,802 | 5,086 | -60% | 1 | 1 | 0% | 2,042 | 3,389 | +66% | 0 | 0 | — |
case-18 | fail→fail | 12,748 | 3,950 | -69% | 1 | 1 | 0% | 2,140 | 2,917 | +36% | 0 | 0 | — |
case-19 | pass→fail | 9,486 | 5,709 | -40% | 1 | 1 | 0% | 1,383 | 2,699 | +95% | 0 | 0 | — |
case-20 | pass→pass | 2,390 | 2,415 | +1% | 1 | 1 | 0% | 354 | 2,777 | +684% | 0 | 0 | — |
case-21 | pass→pass | 8,011 | 2,445 | -69% | 1 | 1 | 0% | 1,283 | 2,793 | +118% | 0 | 0 | — |
case-22 | fail→pass | 11,370 | 1,920 | -83% | 1 | 1 | 0% | 1,698 | 2,767 | +63% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +32 percentage points is the difference between those two pass rates over the 19 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.