wp-ci-qa — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited wp-ci-qa (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Model note: This skill traces non-obvious root causes across PHP, JS, and CI — run onsonnetoropus. Do not downgrade tohaiku; cause identification requires reasoning across multiple files.
Not for: Writing new features or fresh code — use wp-github-flow. Setting up a CI pipeline from scratch — this skill triages failures in existing CI.
Full workflow for diagnosing and fixing bugs reported by QA on an open PR. Starts from the GitHub PR URL, ends with labels updated and a QA re-test comment posted.
scripts/fetch-pr-context.sh <pr> [owner/repo] — Step 1 in one command: printsPR metadata, labels, and the full QA comment thread (newest last).
references/root-cause-patterns.md — catalog of recurring bug patterns(symptom → cause → detect → fix). Read before tracing; append after.
references/qa-comment-template.md — the Step 8 re-test comment template + rules.references/github-actions-wp-matrix.md — PHPUnit/PHPCS/PHPStan workflow configs, PHP×WP version matrix, caching, and CI failure triage.digraph fix_qa {
rankdir=TB;
"Read PR + QA comments" -> "Checkout branch";
"Checkout branch" -> "Trace root causes in code";
"Trace root causes in code" -> "Fix scope-by-scope";
"Fix scope-by-scope" -> "Lint / static analysis";
"Lint / static analysis" -> "Commit each fix separately";
"Commit each fix separately" -> "Push branch";
"Push branch" -> "Update PR labels";
"Update PR labels" -> "Post QA re-test comment";
}scripts/fetch-pr-context.sh <number> <owner/repo>Or manually:
gh pr view <number> --repo <owner/repo> \
--json title,body,author,baseRefName,headRefName,state,labels
gh pr view <number> --repo <owner/repo> --json comments,reviewsCollect:
Testing Failed, Need Testing, etc.)git fetch origin <branch>
git checkout <branch>If the repo is not cloned locally, find it under wp-content/plugins/ or the relevant project path.
Start from the action/hook/controller that handles the broken feature.
First, scan `references/root-cause-patterns.md` — most QA failures match a known pattern (array-cast-to-1, hook-fired-in-one-path, chart-renders-raw-id, duplicate-component-drift, default-margin-misalignment). It gives symptom → detect → fix for each.
Key questions when no known pattern matches:
(grep -rn "do_action( 'hook'")? Is it reached on EVERY path to that state?
get_*() returning wrong values after load() mutates state?render prop was passed? Does a sibling surface have a fix this one lacks?
When you trace a NEW non-obvious cause, append it to the catalog before moving on.
One logical bug = one commit. Do not bundle unrelated fixes.
After each fix, verify the changed files:
# PHP
vendor/bin/phpcs app/Models/ChangedFile.php
# JS/TS lint
yarn lint <files>
# Frontend changes: the build is the real proof (lint/tsc may be noisy)
yarn build # or: node_modules/.bin/wp-scripts buildIf the tooling env is broken (corepack lockfile error, eslintrc circular config, tsc halting on deprecations — all common on machines where global toolchain versions drifted from the lockfile), fall back to the local binary:
node_modules/.bin/eslint <files>
node_modules/.bin/tsc --noEmit --ignoreDeprecations 6.0
node_modules/.bin/wp-scripts buildSeparate pre-existing errors from yours. A noisy lint/tsc run (e.g. 60+ errors) is usually env/version mismatch, not your change. Confirm none of the errors reference your changed files. Pre-existing errors in unrelated files are OK to leave — only fix what your change introduced. The build compiling successfully is the strongest signal a frontend fix is sound.
A silent PHP check is a broken check, not a pass. When composer run lint, composer run analyze or composer run test prints nothing and exits 0, treat it as a failure to run — not as a clean result. PHP tools bootstrap through vendor/autoload.php, and if Composer's autoload.files eagerly loads a WordPress-guarded file:
defined( 'ABSPATH' ) || exit;…then exit runs during autoload, outside WordPress. exit with no argument is status 0, so the tool dies before it analyses a single file and the shell reports success. Nothing distinguishes it from a real pass except the missing output.
Define the constant before the tool boots:
printf '<?php define( "ABSPATH", "/path/to/wp/" );\n' > /tmp/abspath.php
php -d auto_prepend_file=/tmp/abspath.php vendor/bin/phpcs <files>
php -d auto_prepend_file=/tmp/abspath.php vendor/bin/phpunit --filter MyTest
php -d auto_prepend_file=/tmp/abspath.php vendor/bin/phpstan analyse <path> --debugPHPStan needs --debug here: its parallel workers re-bootstrap per process and die the same way, which surfaces as workers crashing rather than a clean result. --debug forces single-threaded analysis.
Prove the tool is live before trusting a clean run. A canary is faster than re-reading the config — feed it something that must fail:
printf '<?php\n$x=1;\n if($x){echo "y";}\n' > /tmp/canary.php
php -d auto_prepend_file=/tmp/abspath.php vendor/bin/phpcs /tmp/canary.php
# Expect a wall of violations. Silence here means the tool is not running at all.Same rule for PHPUnit: a run that reports OK (0 tests) or prints no summary line has not tested your change. Always cite the actual counts when claiming a pass.
On an already-red suite, diff names not totals. Where a suite has pre-existing failures, the totals shift on their own — a flaky test can mask a regression you introduced, or invent one you didn't. Capture the failing test names on both sides and diff those instead:
run() { vendor/bin/phpunit 2>&1 | grep -E '^[0-9]+\) ' | sed 's/^[0-9]*) //' | sort; }
run > /tmp/after.txt
git stash -u && run > /tmp/before.txt && git stash pop
diff /tmp/before.txt /tmp/after.txt # only additions matterLines added = regressions you own. Lines removed = tests you fixed, or flakes. Report it that way rather than as "N failures before, N after".
git add app/Path/To/ChangedFile.php
git commit -m "$(cat <<'EOF'
fix(scope): short imperative summary
Root cause: <what was actually wrong>
Fix: <what was changed and why>
EOF
)"Use fix(scope): prefix. Scope = the subsystem (referral, transaction, email, spa, etc.).
Before committing built assets: check whether build/ (or dist/) is tracked or ignored, and match the repo's convention — don't commit generated output if prior PR commits were source-only:
git check-ignore build/ # prints "build/" if ignored → commit source only
git show --stat <prev-commit> # confirm what prior fix commits includedgit push origin <branch>No new PR needed if the branch already has an open PR — the new commits update it automatically.
Only swap if the current label is wrong for re-test. If the PR is already Need Testing (a re-fix round on a still-open testing cycle), leave it — no change needed. Swap only when moving off a terminal state:
# See available labels first
gh label list --repo <owner/repo>
# Only if currently "Testing Failed"/"Testing Ongoing":
gh pr edit <number> --repo <owner/repo> \
--remove-label "Testing Failed" \
--add-label "Need Testing"Use references/qa-comment-template.md. Comment must include all four:
| Section | Content |
|---|---|
| Summary table | Each broken feature → root cause → fix (1 line each) |
| Step-by-step test instructions | Numbered steps per feature, specific UI path |
| Regression check | Ask QA to re-verify previously working items |
| Commit SHAs | Latest fix commits so QA knows what to test against |
gh pr comment <number> --repo <owner/repo> --body "$(cat <<'EOF'
<filled-in template from references/qa-comment-template.md>
EOF
)"| Mistake | Fix |
|---|---|
| Bundling multiple bug fixes in one commit | One logical fix per commit — easier to revert and for QA to trace |
| Ignoring pre-existing lint errors | Only fix errors your changes introduced |
| Opening a new PR when branch already has one | Just push; the existing PR updates automatically |
| Vague QA comment ("fixed bugs") | Name each broken feature, give exact UI steps |
| Forgetting regression check in QA comment | Always ask QA to confirm previously passing items still pass |
Not verifying do_action call sites | Check ALL places a hook should fire, not just the obvious one |
| Skipping build on frontend PRs | Lint/tsc may be broken or noisy; yarn build compiling is the real proof |
| Treating noisy lint/tsc as your fault | 60+ errors = env/version drift. Confirm none name your files, then proceed |
| Reading "no output, exit 0" from a PHP check as a pass | An ABSPATH-guarded autoload file exits with status 0 before the tool runs. Re-run under auto_prepend_file and confirm with a canary |
| Claiming tests pass without citing counts | OK (0 tests) and a crashed bootstrap both look like success. Quote the summary line |
| Comparing a red suite by eyeballing totals | Flaky tests move the count either way. Diff the failing test names before and after |
Committing gitignored build/ output | Check git check-ignore build/; match prior commits (usually source-only) |
| Swapping a label that's already correct | If already Need Testing on a re-fix round, leave it |
| Reading only the first QA comment | Read the latest — features confirmed fixed in round 1 shouldn't be re-touched |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.