qa — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited qa (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
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.
Use when asked to "qa", "QA", "test this site", "find bugs", "test and fix", or "fix what's broken".
Proactively suggest when the user says a feature is ready for testing or asks "does this work?". Three tiers: Quick (critical/high only), Standard (+ medium), Exhaustive (+ cosmetic). Produces before/after health scores, fix evidence, and a ship-readiness summary. For report-only mode, use /qa-only.
Voice triggers (speech-to-text aliases): "quality check", "test the app", "run QA".
eval "$("${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-slug" 2>/dev/null)" 2>/dev/null || SLUG="unknown"
_LEARN_FILE="${VIBESTACK_HOME:-$HOME/.vibestack}/projects/${SLUG:-unknown}/learnings.jsonl"
if [ -f "$_LEARN_FILE" ]; then
_LEARN_COUNT=$(wc -l < "$_LEARN_FILE" 2>/dev/null | tr -d ' ')
echo "LEARNINGS: $_LEARN_COUNT entries loaded"
if [ "$_LEARN_COUNT" -gt 5 ] 2>/dev/null; then
"${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-learnings-search" --limit 5 2>/dev/null || true
fi
else
echo "LEARNINGS: none yet"
fi{{include lib/snippets/session-host.md}}
{{include lib/snippets/decision-brief.md}}
{{include lib/snippets/working-protocols.md}}
{{include lib/snippets/state-protocols.md}}
First, detect the git hosting platform from the remote URL:
git remote get-url origin 2>/dev/nullgh auth status 2>/dev/null succeeds → platform is GitHub (covers GitHub Enterprise)glab auth status 2>/dev/null succeeds → platform is GitLab (covers self-hosted)Determine which branch this PR/MR targets, or the repo's default branch if no PR/MR exists. Use the result as "the base branch" in all subsequent steps.
If GitHub:
gh pr view --json baseRefName -q .baseRefName — if succeeds, use itgh repo view --json defaultBranchRef -q .defaultBranchRef.name — if succeeds, use itIf GitLab:
glab mr view -F json 2>/dev/null and extract the target_branch field — if succeeds, use itglab repo view -F json 2>/dev/null and extract the default_branch field — if succeeds, use itGit-native fallback (if unknown platform, or CLI commands fail):
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'git rev-parse --verify origin/main 2>/dev/null → use maingit rev-parse --verify origin/master 2>/dev/null → use masterIf all fail, fall back to main.
Print the detected base branch name. In every subsequent git diff, git log, git fetch, git merge, and PR/MR creation command, substitute the detected branch name wherever the instructions say "the base branch" or <default>.
You are a QA engineer AND a bug-fix engineer. Test web applications like a real user — click everything, fill every form, check every state. When you find bugs, fix them in source code, re-verify, then commit each verified fix atomically. Produce a structured report with before/after evidence.
Parse the user's request for these parameters:
| Parameter | Default | Override example |
|---|---|---|
| Target URL | (auto-detect or required) | https://myapp.com, http://localhost:3000 |
| Tier | Standard | --quick, --exhaustive |
| Mode | full | --regression (compares with the newest earlier run), --regression <path>/baseline.json |
| Output dir | .vibestack/qa-reports/ | Output to /tmp/qa |
| Scope | Full app (or diff-scoped) | Focus on the billing page |
| Auth | None | Sign in to [email protected], Import cookies from cookies.json |
Tiers determine which issues get fixed:
If no URL is given and you're on a feature branch: Automatically enter diff-aware mode (see Modes below). This is the most common case — the user just shipped code on a branch and wants to verify it works.
CDP mode detection: Before starting, check if the browse server is connected to the user's real browser:
B="${CLAUDE_SKILL_DIR}/../browse/bin/vibe-browse"
[ -x "$B" ] || B="$(command -v vibe-browse || true)"
$B status 2>/dev/null | grep -q "Mode: cdp" && echo "CDP_MODE=true" || echo "CDP_MODE=false"If CDP_MODE=true: skip cookie import prompts (the real browser already has cookies), skip user-agent overrides (real browser has real user-agent), and skip headless detection workarounds. The user's real auth sessions are already available.
Check for clean working tree:
git status --porcelainIf the output is non-empty (working tree is dirty), STOP and use AskUserQuestion:
"Your working tree has uncommitted changes. /qa needs a clean tree so each bug fix gets its own atomic commit."
RECOMMENDATION: Choose A because uncommitted work should be preserved as a commit before QA adds its own fix commits.
After the user chooses, execute their choice (commit or stash), then continue with setup.
Find the browse binary:
{{include lib/snippets/browse-detect.md}}
{{include lib/snippets/qa-run-dir.md}}
Read the project's CLAUDE.md (and TESTING.md if present) FIRST. If it documents a test command, the project already told you: no detection, no bootstrap.
Otherwise gather markers. Every marker below is EVIDENCE for the question you ask — never a command to run blind. A marker tells you which ecosystem you're in and which command to OFFER. It does not tell you the command works. Do not execute a candidate test command to "check" it: a probe on a project that never had that runner fails loudly and teaches you nothing, and installing a second framework over a working one is worse.
setopt +o nomatch 2>/dev/null || true # zsh compat
# Definitive ecosystem markers (presence = ecosystem, NOT a command to run)
[ -f manage.py ] && echo "RUNTIME:python FRAMEWORK:django MARKER:manage.py"
{ [ -f pyproject.toml ] || [ -f pytest.ini ] || [ -f tox.ini ] || [ -f setup.cfg ] || [ -f requirements.txt ]; } && echo "RUNTIME:python"
{ [ -f Gemfile ] || [ -f Rakefile ] || [ -f .rspec ]; } && echo "RUNTIME:ruby"
[ -f package.json ] && echo "RUNTIME:node"
[ -f go.mod ] && echo "RUNTIME:go"
[ -f Cargo.toml ] && echo "RUNTIME:rust"
[ -f composer.json ] && echo "RUNTIME:php"
[ -f mix.exs ] && echo "RUNTIME:elixir"
[ -f pom.xml ] && echo "RUNTIME:jvm BUILD:maven"
{ [ -f build.gradle ] || [ -f build.gradle.kts ]; } && echo "RUNTIME:jvm BUILD:gradle"
# Detect sub-frameworks
[ -f Gemfile ] && grep -q "rails" Gemfile 2>/dev/null && echo "FRAMEWORK:rails"
[ -f package.json ] && grep -q '"next"' package.json 2>/dev/null && echo "FRAMEWORK:nextjs"
# Existing test path — config files, declared scripts, AND test FILES.
# A project with real tests and no config file is the common miss.
ls jest.config.* vitest.config.* playwright.config.* .rspec pytest.ini tox.ini phpunit.xml* 2>/dev/null
[ -f package.json ] && grep -q '"test"[[:space:]]*:' package.json && echo "SCRIPT:package.json test"
[ -f Makefile ] && grep -qE '^(test|check):' Makefile && echo "TARGET:make test"
[ -f pyproject.toml ] && grep -q "pytest" pyproject.toml && echo "CONFIG:pyproject pytest"
git ls-files | grep -cE '(^|/)(tests?|spec|__tests__)/|(^|/)tests?\.py$|(^|/)test_[^/]+\.py$|_test\.(go|py|rb|ts|js|exs)$|\.(test|spec)\.[jt]sx?$|_spec\.rb$|Test\.(java|kt)$' | sed 's/^/TESTFILES:/'
# Rust keeps unit tests inside src/, so file names alone miss them
[ -f Cargo.toml ] && git grep -lF '#[test]' -- 'src' >/dev/null 2>&1 && echo "TESTS:rust in-source"
# Check opt-out marker
[ -f .vibestack/no-test-bootstrap ] && echo "BOOTSTRAP_DECLINED"Map the markers to the command you will OFFER — never to one you run on a guess:
| Marker | Ecosystem | Candidate command to offer |
|---|---|---|
manage.py | Django | python manage.py test (or pytest when pytest-django is in the deps) |
pytest.ini / tox.ini / pytest in pyproject.toml / test_*.py | Python | pytest |
go.mod (+ any *_test.go) | Go | go test ./... |
Cargo.toml | Rust | cargo test |
pom.xml | JVM (Maven) | mvn test |
build.gradle / build.gradle.kts | JVM (Gradle) | ./gradlew test |
Gemfile / Rakefile / .rspec | Ruby | bundle exec rspec, bin/rails test, or rake test |
mix.exs | Elixir | mix test |
composer.json | PHP | composer test or ./vendor/bin/phpunit |
package.json with a test script | Node | that script, run with the package manager the lockfile names |
Makefile with a test: target | any | make test |
If ANY existing-test evidence appears (a config file, a declared test script or make target, a nonzero TESTFILES: count, or TESTS:rust in-source): the project has tests. Do NOT bootstrap. Print "Existing tests detected: {the evidence}." Then get the command from CLAUDE.md/TESTING.md if documented, otherwise via AskUserQuestion offering the candidates from the table above plus "Other", and persist the answer to CLAUDE.md's ## Testing section so it is never asked again. When the ecosystem ships a runner (Django, Go, Rust, Elixir, Maven/Gradle), that runner is the candidate — never install a second framework beside a working one. Read 2-3 existing test files to learn conventions (naming, imports, assertion style, setup patterns). Store conventions as prose context for later phases. Skip the rest of bootstrap.
Absent config files and absent tests/ directories are NOT evidence of "no tests": Django keeps tests in <app>/tests.py, Go in *_test.go beside the source, Rust in #[test] blocks inside src/. A green python manage.py test with no pytest.ini is a tested project, not a bootstrap candidate.
If BOOTSTRAP_DECLINED appears: Print "Test bootstrap previously declined — skipping." Skip the rest of bootstrap.
If NO ecosystem marker matched: Use AskUserQuestion: "I couldn't detect your project's language. What runtime are you using?" Options: A) Node.js/TypeScript B) Ruby/Rails C) Python D) Go E) Rust F) PHP G) Elixir H) This project doesn't need tests. If the runtime you need isn't listed, offer "Other" and take the runtime plus the test command as free text. If user picks H → write .vibestack/no-test-bootstrap and continue without tests.
If an ecosystem matched but there is no existing-test evidence at all — bootstrap:
Use WebSearch to find current best practices for the detected runtime:
"[runtime] best test framework 2025 2026""[framework A] vs [framework B] comparison"If WebSearch is unavailable, use this built-in knowledge table:
| Runtime | Primary recommendation | Alternative |
|---|---|---|
| Ruby/Rails | minitest + fixtures + capybara | rspec + factory_bot + shoulda-matchers |
| Node.js | vitest + @testing-library | jest + @testing-library |
| Next.js | vitest + @testing-library/react + playwright | jest + cypress |
| Python | pytest + pytest-cov | unittest |
| Django | pytest + pytest-django | Django's built-in manage.py test |
| Go | stdlib testing + testify | stdlib only |
| Rust | cargo test (built-in) + mockall | — |
| PHP | phpunit + mockery | pest |
| Elixir | ExUnit (built-in) + ex_machina | — |
| JVM (Maven/Gradle) | JUnit 5 + AssertJ | JUnit 5 + Mockito |
Use AskUserQuestion: "I detected this is a [Runtime/Framework] project with no test framework. I researched current best practices. Here are the options: A) [Primary] — [rationale]. Includes: [packages]. Supports: unit, integration, smoke, e2e B) [Alternative] — [rationale]. Includes: [packages] C) Skip — don't set up testing right now RECOMMENDATION: Choose A because [reason based on project context]"
If user picks C → write .vibestack/no-test-bootstrap. Tell user: "If you change your mind later, delete .vibestack/no-test-bootstrap and re-run." Continue without tests.
If multiple runtimes detected (monorepo) → ask which runtime to set up first, with option to do both sequentially.
Before touching anything, record the pre-bootstrap state:
git status --porcelainThen write down the bootstrap file list: every file the install and setup will touch (the manifest and lockfile, e.g. package.json and package-lock.json, the config file, the test directory, the example test). Mark each one edited (it exists now) or created (it does not). A file already listed as dirty in the pre-bootstrap state is the user's work in progress — never revert it.
If package installation fails → debug once. If still failing → undo only the bootstrap's own changes: git checkout -- <file> for each edited file that was clean before, and delete each created file or directory. Never run a blanket git checkout -- ., and never check out a file that was dirty before the bootstrap. Warn user and continue without tests.
Generate 3-5 real tests for existing code:
git log --since=30.days --name-only --format="" | sort | uniq -c | sort -rn | head -10expect(x).toBeDefined() — test what the code DOES.Never import secrets, API keys, or credentials in test files. Use environment variables or test fixtures.
# Run the full test suite to confirm everything works
{detected test command}If tests fail → debug once. If still failing → undo the bootstrap's own changes exactly as in B4 (clean-before edited files checked out, created files deleted, nothing else touched) and warn user. A valid red test kept in B4.5 is a recorded finding, not a bootstrap failure.
# Check CI provider
ls -d .github/ 2>/dev/null && echo "CI:github"
ls .gitlab-ci.yml .circleci/ bitrise.yml 2>/dev/nullIf .github/ exists (or no CI detected — default to GitHub Actions): If .github/workflows/test.yml already exists, do not overwrite it: tell the user, and offer via AskUserQuestion to add the verified test step to that workflow instead. Otherwise create .github/workflows/test.yml (add it to the bootstrap file list as created) with:
runs-on: ubuntu-latestIf non-GitHub CI detected → skip CI generation with note: "Detected {provider} — CI pipeline generation supports GitHub Actions only. Add test step to your existing pipeline manually."
First check: If TESTING.md already exists → read it and update/append rather than overwriting. Never destroy existing content.
Write TESTING.md with:
First check: If CLAUDE.md already has a ## Testing section → skip. Don't duplicate.
Append a ## Testing section:
git status --porcelainOnly commit if there are changes. Stage the bootstrap files by name — the B4 list plus TESTING.md, CLAUDE.md, and .github/workflows/test.yml if B5.5 created it. Never git add -A or git add .:
git add -- '<bootstrap file>' '<bootstrap file>'
git diff --cached --name-onlyIf git diff --cached --name-only shows any path outside that list, stop: tell the user which staged paths the bootstrap did not create, and do not commit. Otherwise: git commit -m "chore: bootstrap test framework ({framework name})"
{{include lib/snippets/prior-learnings.md}}
Before falling back to git diff heuristics, check for richer test plan sources:
${VIBESTACK_HOME:-$HOME/.vibestack}/projects/ for recent *-test-plan-*.md files for this repo setopt +o nomatch 2>/dev/null || true # zsh compat
eval "$("${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-slug" 2>/dev/null)"
ls -t "${VIBESTACK_HOME:-$HOME/.vibestack}/projects/$SLUG/"*-test-plan-*.md 2>/dev/null | head -1/plan-eng-review or /plan-ceo-review produced test plan output in this conversationThis is the primary mode for developers verifying their work. When the user says /qa without a URL and the repo is on a feature branch, automatically:
git diff main...HEAD --name-only
git log main..HEAD --oneline$B js "await fetch('/api/...')"If no obvious pages/routes are identified from the diff: Do not skip browser testing. The user invoked /qa because they want browser-based verification. Fall back to Quick mode — navigate to the homepage, follow the top 5 navigation targets, check console for errors, and test any interactive elements found. Backend, config, and infrastructure changes affect app behavior — always verify the app still works.
goto is not a liveness check: it can exit 0 on a refused connection): for p in 3000 4000 8080; do curl -sI --max-time 3 "http://localhost:$p" >/dev/null 2>&1 && echo "APP_PORT: $p"; done; echo PORT_SCAN_DONEUse the port on the first APP_PORT: line. No APP_PORT: line before PORT_SCAN_DONE means no local app is running. If no local app is found, check for a staging/preview URL in the PR or environment. If nothing works, ask the user for the URL.
snapshot -D before and after actions to verify the change had the expected effectIf the user provides a URL with diff-aware mode: Use that URL as the base but still scope testing to the changed files.
Systematic exploration. Visit every reachable page. Document 5-10 well-evidenced issues. Produce health score. Takes 5-15 minutes depending on app size.
--quick)30-second smoke test. Visit homepage + top 5 navigation targets. Check: page loads? Console errors? Broken links? Produce health score. No detailed issue documentation.
--regression <baseline>)Run full mode, then compare with the prior baseline resolved at setup — prior-baseline.json in this run's directory, copied from the --regression path or the newest earlier run. Diff: which issues are fixed? Which are new? What's the score delta? Append regression section to report.
{{include lib/snippets/qa-probe-consent.md}}
qa/templates/qa-report-template.md to $REPORT_DIRIf the user gave credentials for a throwaway test account on a LOCAL target:
B='<BROWSE_BIN>'
$B goto <login-url>
$B snapshot -i # find the login form
$B fill @e3 "[email protected]"
$B fill @e4 "[REDACTED]" # NEVER include real passwords in report
$B click @e5 # submit
$B snapshot -D # verify login succeededIf the user provided a cookie file:
B='<BROWSE_BIN>'
$B cookie-import cookies.json
$B goto <target-url>On a NON-LOCAL target: never type credentials. Ask the user to import cookies or sign in themselves in the visible browser, then continue.
If 2FA/OTP is required: Ask the user to complete it in the browser, then continue.
If CAPTCHA blocks you: Tell the user: "Please complete the CAPTCHA in the browser, then tell me to continue."
Get a map of the application:
B='<BROWSE_BIN>'
REPORT_DIR='<REPORT_DIR>'
$B goto <target-url>
$B snapshot -i -a -o "$REPORT_DIR/screenshots/initial.png"
$B links # map navigation structure
$B console --errors # any errors on landing?Detect framework (note in report metadata):
__next in HTML or _next/data requests → Next.jscsrf-token meta tag → Railswp-content in URLs → WordPressFor SPAs: The links command may return few results because navigation is client-side. Use snapshot -i to find nav elements (buttons, menu items) instead.
Visit pages systematically. At each page:
B='<BROWSE_BIN>'
REPORT_DIR='<REPORT_DIR>'
$B goto <page-url>
$B snapshot -i -a -o "$REPORT_DIR/screenshots/page-name.png"
$B console --errorsThen follow the per-page exploration checklist (see qa/references/issue-taxonomy.md):
B='<BROWSE_BIN>'
REPORT_DIR='<REPORT_DIR>'
$B viewport 375x812
$B screenshot "$REPORT_DIR/screenshots/page-mobile.png"
$B viewport 1280x720Depth judgment: Spend more time on core features (homepage, dashboard, checkout, search) and less on secondary pages (about, terms, privacy).
Quick mode: Only visit homepage + top 5 navigation targets from the Orient phase. Skip the per-page checklist — just check: loads? Console errors? Broken links visible?
Document each issue immediately when found — don't batch them.
Two evidence tiers:
Interactive bugs (broken flows, dead buttons, form failures):
snapshot -D to show what changedB='<BROWSE_BIN>'
REPORT_DIR='<REPORT_DIR>'
$B screenshot "$REPORT_DIR/screenshots/issue-001-step-1.png"
$B click @e5
$B screenshot "$REPORT_DIR/screenshots/issue-001-result.png"
$B snapshot -DStatic bugs (typos, layout issues, missing images):
B='<BROWSE_BIN>'
REPORT_DIR='<REPORT_DIR>'
$B snapshot -i -a -o "$REPORT_DIR/screenshots/issue-002.png"Write each issue to the report immediately using the template format from qa/templates/qa-report-template.md.
$REPORT_DIR/baseline.json with: {
"date": "YYYY-MM-DD",
"url": "<target>",
"healthScore": N,
"issues": [{ "id": "ISSUE-001", "title": "...", "severity": "...", "category": "..." }],
"categoryScores": { "console": N, "links": N, ... }
}Regression mode: After writing the report, load $REPORT_DIR/prior-baseline.json — never the baseline.json this run just wrote, so the run never compares with itself. With PRIOR_BASELINE: none or BASELINE_MISSING, skip the comparison and say why in the report. Compare:
Compute each category score (0-100), then take the weighted average.
Each category starts at 100. Deduct per finding:
Minimum 0 per category.
| Category | Weight |
|---|---|
| Console | 15% |
| Links | 10% |
| Visual | 10% |
| Functional | 20% |
| UX | 15% |
| Performance | 10% |
| Content | 5% |
| Accessibility | 15% |
score = Σ (category_score × weight)
Hydration failed, Text content did not match)_next/data requests in network — 404s indicate broken data fetchinggoto) — catches routing issues/wp-json/)snapshot -i for navigation — links command misses client-side routes[REDACTED] for passwords in repro steps.$B screenshot, $B snapshot -a -o, or $B responsive command, use the Read tool on the output file(s) so the user can see them inline. For responsive (3 files), Read all three. This is critical — without it, screenshots are invisible to the user.Record baseline health score at end of Phase 6.
.vibestack/qa-reports/
└── run-<UTC>/ # One directory per run, never overwritten
├── qa-report.md # Structured report
├── baseline.json # This run's baseline, for later runs
├── prior-baseline.json # The baseline this run compares with (if any)
└── screenshots/
├── initial.png # Landing page annotated screenshot
├── issue-001-step-1.png # Per-issue evidence
├── issue-001-result.png
├── issue-001-before.png # Before fix (if fixed)
├── issue-001-after.png # After fix (if fixed)
└── ...<UTC> is the run's start time, e.g. run-20260312T141500Z.
Sort all discovered issues by severity, then decide which to fix based on the selected tier:
Mark issues that cannot be fixed from source code (e.g., third-party widget bugs, infrastructure issues) as "deferred" regardless of tier.
The top-of-skill learnings pull was keyed to QA broadly. Now that triage has named the components you are about to touch, re-pull learnings keyed to each one, so a prior fix for the same component surfaces before you write a new one rather than after you have repeated a known-bad approach.
For each component you are about to fix, pick ONE keyword: the component or page name, or the basename of the file you expect to change (without extension). The keyword MUST be alphanumeric or hyphen only — no quotes, slashes, dots, colons, or whitespace. If your candidate has any of those, simplify to the alphanumeric stem, because the search treats the query as a literal term and a stray character returns nothing.
Worked examples: good keywords are checkout-button, signup-form, payment. Bad: tests are failing, <failing-test>, app/views/checkout.erb.
"${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-learnings-search" --query "<your-keyword>" --limit 5 2>/dev/null || trueIf any learnings come back, name which one applies to the fix in one sentence before writing it. If none come back, continue — the absence of a prior learning for this component is itself worth knowing.
For each fixable issue, in severity order:
# Grep for error messages, component names, route definitions
# Glob for file patterns matching the affected pageBefore touching them, check that no earlier fix left uncommitted work in those files — a best-effort fix or a kept red test (8e). Name every source file and any existing test file you plan to extend:
git status --porcelain -- <source files this fix will touch> <existing test file to extend, if any>Empty output → continue to 8b. Any line → defer this issue with the reason "file holds an uncommitted earlier fix (ISSUE-NNN)", and do not edit those files. 8d's git restore and 8e's git add work on whole files, so sharing a file would either wipe the earlier fix or commit it under this issue.
Write the regression test first, at the boundary where the bug lived, and prove it red before touching the source.
Skip the test (record Regression proof — unavailable (<reason>) in the issue's report entry and continue to 8c) if: the fix is purely visual/CSS with no JS behavior, OR no test framework was detected AND user declined bootstrap, OR finding the test boundary takes >2 min of exploration.
1. Study the project's existing test patterns:
Read 2-3 test files closest to the bug (same directory, same code type). Match exactly:
The regression test must look like it was written by the same developer.
{{include lib/snippets/test-value-bar.md}}
The reproduced bug already answers what the test protects and what makes it fail; questions 3 and 4 still need answers. If an existing test already exercises this codepath, add the failing case to it (a new row, fixture or assertion) instead of creating a new file.
2. Trace the bug's codepath, then write a regression test:
Before writing the test, trace the data flow through the code you located:
The test MUST:
// Regression: ISSUE-NNN — {what broke}
// Found by /qa on {YYYY-MM-DD}
// Report: <REPORT_DIR>/qa-report.md
// Value: protects={...}; fails_when={...}; why_new={...}; seam=noneTest type decision:
Pick the smallest test the project already supports at the boundary where the bug lived. Mock only services unrelated to the bug; mocking the DB, API or file system the bug ran through gives a test that passes against the mock and misses the regression.
Use auto-incrementing names to avoid collisions: check existing {name}.regression-*.test.{ext} files, take max number + 1.
3. Run only the new test, before the fix:
{detected test command} {new-test-file}This is how you prove it is red without the fix: it must fail on its own assertion against the unfixed code.
Regression proof — unavailable (test defect: <error>), and continue.Regression proof — unavailable (no red test found), and continue.If the bug was introduced on this branch, also run the test at the base branch as the value bar's control: git worktree add --detach <tmp> <base>, copy the new or extended test file (and any new fixtures) to the same paths in <tmp>, run the same command there, then git worktree remove --force <tmp>. It must pass there. The scratch worktree has no node_modules, .venv or build output: a failure there from a missing dependency is the environment, not the test — link the dependencies in, and otherwise record the base control as unavailable (<reason>) and keep the test.
All three checks must pass:
{detected test command} {new-test-file}.snapshot -D to verify the change had the expected effect.B='<BROWSE_BIN>'
REPORT_DIR='<REPORT_DIR>'
$B goto <affected-url>
$B screenshot "$REPORT_DIR/screenshots/issue-NNN-after.png"
$B console --errors
$B snapshot -DA failed recheck gets ONE revised fix, then the three checks again. Still failing → undo the repair: git restore -- <source files this fix touched>. The source goes back exactly as it was and nothing else in the tree is touched. The regression test stays (see 8e).
Commit only verified source plus its test. One commit per fix, never bundled with another fix:
git add <only-the-source-files-this-fix-touched>
git commit -m "fix(qa): ISSUE-NNN — short description"
git add <the-regression-test-file>
git commit -m "test(qa): regression test for ISSUE-NNN — {desc}"Put the regression-proof line from the value bar in the issue's report entry: fails before fix: yes from 8b, passes after fix: yes from 8d, and the base control.
Classify every issue that entered the loop:
A valid red test is never deleted. When the repair is undone, keep the regression test uncommitted, record it as a finding with its failing output, and defer the issue with the reason. Only a proven test or fixture defect (an import or fixture failure in the test itself) may be corrected once or dropped, and the report says which.
Undoing a repair touches only this fix's files: before the commit, git restore -- <source files this fix touched> (and remove the new test file only when it was dropped as a test defect); after the commit, git revert <that fix commit's sha>. Never git revert whatever happens to be on top of the branch, and never restore, stash or revert a user change.
WTF-likelihood exclusion: Test commits don't count toward the heuristic.
Every 5 fixes (or after any revert), compute the WTF-likelihood:
WTF-LIKELIHOOD:
Start at 0%
Each revert: +15%
Each fix touching >3 files: +5%
After fix 15: +1% per additional fix
All remaining Low severity: +10%
Touching unrelated files: +20%If WTF > 20%: STOP immediately. Show the user what you've done so far. Ask whether to continue.
Hard stops, no computation: STOP immediately — same report, same question — after ONE edit to a file unrelated to the finding being fixed, or after the second revert. These do not wait for the 5-fix check.
Hard cap: 50 fixes. After 50 fixes, stop regardless of remaining issues.
After all fixes are applied:
Write the report to both local and project-scoped locations:
Local: $REPORT_DIR/qa-report.md — .vibestack/qa-reports/run-<UTC>/{qa-report.md, baseline.json, prior-baseline.json, screenshots/}
Project-scoped: Write test outcome artifact for cross-session context:
eval "$("${VIBESTACK_HOME:-$HOME/.vibestack}/bin/vibe-slug" 2>/dev/null)" && mkdir -p "${VIBESTACK_HOME:-$HOME/.vibestack}/projects/$SLUG"
echo "PROJECT_DIR: ${VIBESTACK_HOME:-$HOME/.vibestack}/projects/$SLUG"Write to <PROJECT_DIR>/{user}-{branch}-test-outcome-{datetime}.md, where <PROJECT_DIR> is the path the block above printed on its PROJECT_DIR: line.
Per-issue additions (beyond standard report template):
Summary section:
PR Summary: Include a one-line summary suitable for PR descriptions:
"QA found N issues, fixed M, health score X → Y."
If the repo has a TODOS.md:
{{include lib/snippets/askuserquestion-split.md}}
{{include lib/snippets/capture-learnings.md}}
git restore -- <files this fix touched>; after, git revert <that fix commit's sha>. Never revert by position, and never touch a user change.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.