qa
Use when verifying a code change through a standalone adversarial e2e cycle — drives the changed surface for real (curl/agent-browser/maestro/bash) and attacks it across the 6-category matrix (failure/boundary/injection/interruption/misleading-success/idempotency), owning diagnosis→fix→re-verify to green via `oracle` (diagnosis) and `sisyphus-junior` (fix) before issuing a binary APPROVE/REQUEST_CHANGES verdict.
适合你,如果需要确保代码变更通过全面测试并自动修复问题
npx oh-my-skill add toongri/oh-my-toong-playground/qacurl -fsSL https://oh-my-skill.com/install.sh | bash -s -- toongri/oh-my-toong-playground/qanpx oh-my-skill verify toongri/oh-my-toong-playground/qa怎么用
技能原文 SKILL.md
<Role>
QA
Core Principle: Nothing ships without proof, and the fixer never certifies its own fix. qa drives the real application, attacks it, and — if it fails — owns the diagnose→fix→re-verify loop through independent agents until the surface is actually green.
Overview
Pure dynamic adversarial-e2e verification skill. qa reads the change to author high-coverage scenarios and proves them by execution: static document-vs-code auditing (Security/Data-Integrity checklists, MUST-DO compliance tables, Completeness prose audits) stays code-review's job; the behavior-invisible PRE-FLIGHT contract gate below is a narrow exception, not a static-audit stand-in.
qa is standalone and stateful. A single invocation owns the whole cycle — detection, diagnosis, fix, and re-verification — through to a final verdict, persisting its phase/cycle to a state file so an interrupted run can resume with continue.
Standards: The application actually runs, survives hostile probing across all 6 adversarial categories, and any regression introduced while fixing it is caught by a fresh full re-run, not the fixer's own say-so.
</Role>
QA REQUEST Format
The caller composes a QA REQUEST using this structure:
# QA REQUEST ## Spec [WHAT to verify — requirements, criteria, constraints, MUST-NOT-DO scope] ## Required Verification [HOW to verify — verification commands, QA scenarios, evidence paths to collect. Optional but standard for sisyphus-orchestrated QA requests; see `skills/sisyphus/verification.md` §QA REQUEST Composition for the canonical recipe.] ## Scope - Changed files: - [explicit file paths] - Summary: [what the implementer claimed]
#QA REQUEST →##Spec / Required Verification / Scope →###internal subsections- The content of Spec is PLAN's input: it determines the verification targets and the adversarial scenarios PLAN derives.
Required Verificationis used when sisyphus explicitly passes verification commands and evidence paths — BASELINE and ADVERSARIAL E2E execute the section's commands verbatim and store evidence at the declared paths.- When a delegation prompt is included, its sections become
###headings under## Spec
To understand what changed, use git diff $(git merge-base HEAD main) -- <path> for context. If main does not exist, substitute master. To verify correctness, read the actual files directly (Read tool). Do not independently discover which files changed — use the file list from the QA REQUEST Scope.
The Cycle
qa runs a single stateful cycle, in order:
PRE-FLIGHT → PLAN → BASELINE → ADVERSARIAL E2E → CHECK → [DIAGNOSIS → FIX → RE-VERIFY loop, ≤5 cycles] → EXIT → CLEANUP → ROLLBACK → STATE
Every phase below runs once per pass, except the bracketed loop, which repeats on CHECK failure until an EXIT condition fires.
PRE-FLIGHT
A behavior-invisible contract check — a narrow exception to qa's dynamic-only posture, because no amount of running the app surfaces a scope violation. Gates on exactly two things:
- MUST-NOT-DO scope membership. A changed file violates the contract iff it matches the QA REQUEST's MUST-NOT-DO scope — no positive allowlist, no per-invocation judgment call. Tests and config files are NOT special-cased: they are violations only if the MUST-NOT-DO explicitly names them, and clean otherwise.
- B ⊆ A scope boundary. Expected files (from EXPECTED OUTCOME) = A; Changed files (from QA REQUEST Scope) = B. PASS if B ⊆ A.
On violation: immediate REQUEST_CHANGES, cycle NOT executed — fail-fast. The expensive cycle below never runs against a change that already fails its own declared contract.
PRE-FLIGHT also captures the ROLLBACK safety baseline (used only if the loop below runs): snapshot git status --porcelain as user_dirty_set (the user's pre-existing dirty/uncommitted files) plus current HEAD — each entry is a porcelain status line (XY <path>); the file path is the portion after the status code (accounting for rename old -> new syntax).
PLAN
Parse the QA REQUEST's Spec/AC into concrete verification targets and adversarial scenarios: what BASELINE must run green, what ADVERSARIAL E2E must attack, and what CHECK will judge against. Spec/AC understanding is retained here in full; MUST-DO tables and Completeness sub-checks are code-review's static-audit territory, not PLAN's.
See [scenario-authoring.md] for the risk/coverage-gap derivation framework.
BASELINE
Build/test/lint green baseline.
- Discover project commands: check
~/.omt/{project}/project-commands.mdcache first, thenCLAUDE.md/README.md/build files, then ask the user. Save discovered commands back to the cache. - Run: Build (fast build/typecheck) → Tests → Lint. Slow native build (e.g. an RN bundle) runs only when native code changed or this is a release build (native-code-or-release) — otherwise skip it.
- Save the full output of each check as an evidence file (see Evidence Saving Protocol below).
- ANY failure = immediate REQUEST_CHANGES.
See [stage1-commands.md] for command-discovery detail, special cases (no tests for changed code, no build system), and output format — the content there is BASELINE's detail target, not a separate stage.
ADVERSARIAL E2E
Drive the changed surface for real and attack it. Two parts, both required when the change touches a risk surface — user-facing OR an internal risk surface (feature-flag-gated logic, payment/notification resolver internals, permission/state transitions), per [stage3-handson.md] ### Decision Logic; only a genuinely inert refactor that touches no risk surface skips:
- Execute caller-provided scenarios verbatim, with per-scenario evidence. ANY provided-scenario failure = immediate REQUEST_CHANGES. Caller-provided scenarios always run verbatim, unchanged — the derivation framework below governs only scenarios qa self-authors; it never rewrites what the caller handed in.
- Self-author the 6-category adversarial matrix for the changed surface, in this order — breadth before depth:
- Derive candidate scenarios by breadth via [scenario-authoring.md]: Layer A impact-map → coverage-gap → H/M/L priority.
- Attack each derived scenario with the applicable depth rows, working highest-priority (H) first, from the 6-category matrix: failure paths, boundary/malformed input, injection, interruption-resume + dirty state, misleading success, idempotency. See [stage3-handson.md]
## Adversarial Scenario Matrixfor the full matrix and the lifecycle/applicability detail (start → verify → stop).
Breadth (step 1) MUST precede depth (step 2) — do not skip straight to the matrix on an undifferentiated changed-file list.
Inline modality drivers, no tmux. qa itself drives the modality-appropriate tool inline — it is not delegated to a separate driver subagent:
| Change Type | Driver | |-------------|--------| | API endpoint | curl | | Frontend / UI | agent-browser (fallback: playwright) | | Mobile / App | maestro | | CLI / TUI | interactive bash |
An internal risk surface with no direct UI/API is driven via its nearest entry point — an API/curl call if reachable, or a bash harness that invokes the code path directly.
Command execution is non-blocking only: every command either returns control on its own or is explicitly backgrounded (run_in_background, or trailing & with output redirected). A bare blocking command that hangs the shell is forbidden. See [stage3-handson.md] Step 3.2 for the lifecycle this backs (start in background → wait for readiness → verify → stop, never leaving a server running).
By-design non-idempotency note: running ADVERSARIAL E2E actually exercises the application (starts servers, sends requests, mutates state); some operations under test are intentionally non-idempotent per spec, which is not itself a defect.
CHECK
Is the goal met — BASELINE green and the full ADVERSARIAL E2E pass (provided scenarios + matrix)?
- Pass → PASS. Emit APPROVE (see Output Format).
- Fail → enter the loop below.
DIAGNOSIS → FIX → RE-VERIFY (loop, ≤5 cycles)
CHECK failure hands off to a three-way-separated loop so the agent that fixes the defect is never the one that certifies the fix:
DIAGNOSIS
delegate to oracle (fresh, read-only, root cause + file:line). oracle never modifies files; it returns a diagnosis, not a patch.
FIX
delegate to sisyphus-junior. sisyphus-junior commits its own scoped fix — it authored the hunks, so it alone can stage them precisely; qa cannot separate a fix's hunks from a user's hunks in a shared file. Never git commit -a. qa records fix_head_before = HEAD at FIX dispatch, before this commit is made.
Overlap refusal: if the FIX phase determines the fix must touch a file already in user_dirty_set (captured at PRE-FLIGHT) — overlap is matched on the path parsed from each porcelain entry, i.e. stripping the leading XY status code — qa REFUSE the cycle at FIX with an explicit error ("file X has your uncommitted changes — commit or stash before qa can safely fix it") rather than let the fix's commit sweep the user's uncommitted hunks. This is a structural refusal, not a detect-after-the-fact check.
cycle++ happens here — cycle++ at FIX dispatch is the counted unit (pre-fix detection is cycle 0, uncounted).
RE-VERIFY
qa re-runs BASELINE + the FULL matrix from scratch — not just the failed scenario. Distrust the fixer's report: sisyphus-junior's own claim of "fixed" is not evidence; only a fresh, from-scratch re-run counts. Running the full matrix (not only the scenario that failed) is what catches a fix that silently regresses a scenario that was previously green.
Loop back to CHECK. Continue until an EXIT condition below fires.
EXIT
| Condition | Trigger | Action | |-----------|---------|--------| | Goal Met | CHECK passes (BASELINE + full matrix green) | PASS → APPROVE | | max_cycles=5 | cycle reaches max_cycles (5) still unresolved | Terminate, report unresolved with last diagnosis | | Same-Failure-3x | The same failure repeats 3 times | Terminate, report thrash | | Safety | A safety invariant (e.g. ROLLBACK guard) refuses to proceed | Terminate, report the refusal reason |
- Same-Failure key =
scenario-id + root-cause-file + root-cause-symbol/category(not:line— line numbers shift under fixes and would falsely reset the counter). Two failures are "the same" iff this key matches; the count resets to 1 when a different key appears. - max-N boundary: with
cyclestarting at 0 andcycle++ at FIX dispatch,max_cycles=5permits exactly 5 fix attempts (cycles 1..5); the 5th fix is attempted and re-verified, then EXIT fires if still unresolved.
CLEANUP
Kill every process and remove every artifact this cycle spawned (background servers, simulators/emulators started for ADVERSARIAL E2E, temp files) — regardless of whether the cycle ended in PASS or an EXIT condition. A leaked process corrupts the next run.
ROLLBACK
On a regression caught by RE-VERIFY, qa reverts only its own cycle's commit(s) — never touching the user's pre-existing work:
- Mechanism:
git revert fix_head_before..HEAD(non-destructive). NEVERgit reset --hard— it would destroy all working-tree dirty state, including the disjointuser_dirty_setfiles, with no way to recover content that was never committed. - Three guards, evaluated independently (no guard is skipped because an earlier one passed):
- Linear-descendant guard — assert
HEADis a linear descendant offix_head_before(git merge-base --is-ancestor fix_head_before HEAD). If not (history was amended/rebased), refuse the revert rather than risk reverting into pre-existing content. - Non-empty-range guard — if
fix_head_before == HEAD(no commit was actually made), this is ERROR, not silent success: report ROLLBACK failure, do not exit-0 with the regression silently retained. - Post-revert disjointness assertion — after
git revert, re-rungit status --porcelain, filter to the paths recorded inuser_dirty_set, and compare those lines byte-for-byte against the stored PRE-FLIGHT porcelain lines; the result must be byte-identical. Any drift is contamination and a hard failure. rm -rf/force-flag operations remain auto-denied throughout — ROLLBACK never bypasses that gate.
STATE
Persist phase/cycle (plus max_cycles, same_failure_key/same_failure_count, fix_head_before, user_dirty_set) to a state file after every phase transition, via:
bun ${CLAUDE_SKILL_DIR}/scripts/qa-state.ts <sub>
A continue invocation reads this state and resumes at the last recorded phase/cycle rather than restarting the cycle from PRE-FLIGHT. (The CLI itself is authored elsewhere — this section only pins the invocation contract qa's cycle relies on.)
Once the cycle concludes (any EXIT outcome — Goal Met, max_cycles, Same-Failure-3x, or Safety), run bun ${CLAUDE_SKILL_DIR}/scripts/qa-state.ts complete before reporting the verdict. This marks the state inactive so the finished cycle is not resurrected as "in progress" in a later session.
Fix-Loop Nesting Contract
qa's fix-loop (DIAGNOSIS → FIX → RE-VERIFY) must NOT be called inside another fix-loop (e.g. goal's own pursuit loop) — running one fix-loop inside another double-loops retries and confuses which loop owns EXIT. This is a documented contract, not an enforced one: YAGNI — no detection-guard code is written for a caller that does not exist yet. Named upgrade trigger: add a code guard when qa gains its first fix-loop-owning caller.
Evidence Saving Protocol
Core Rule
Every verification command execution (BASELINE, ADVERSARIAL E2E, RE-VERIFY) produces an evidence file. Evidence files are the audit trail; downstream gates check for their existence before accepting a verdict.
Objective vs. Subjective
| Output Type | Disposition | Examples | |-------------|-------------|---------| | Objective command output | Save to file | build/test/lint logs, curl response body + status, agent-browser/Playwright/Maestro screenshots and reports, CLI execution logs | | Subjective judgment | Response only (no file) | PLAN's spec/AC reading, oracle's diagnosis narrative |
Evidence File Content Requirements
Evidence files must contain meaningful content that demonstrates the verification result. Empty (0-byte) files are not valid evidence. When a command produces empty stdout, record the command executed and its exit code so the file is not empty.
Evidence Path Priority (3-Tier)
- Explicit path from QA REQUEST — caller explicitly provided a path
- Plan QA Scenario Evidence field — see [plan-template.md QA Scenarios](../prometheus/plan-template.md#qa-scenarios-mandatory-per-todo)
- Auto-generated path (fallback): ``` $OMT_DIR/evidence/{work-slug}/{task-slug}/{check-slug}.{ext} ``` Ensure the target directory exists before saving (
mkdir -p).
Evidence Reporting in Response
After the cycle completes, include a ## Evidence Files section listing every evidence file saved, with $OMT_DIR expanded to its absolute path:
## Evidence Files - /Users/dev/.omt/my-project/evidence/add-user-endpoint/implement-user-service/build.txt - /Users/dev/.omt/my-project/evidence/add-user-endpoint/implement-user-service/npm-test.txt
Omit this section when no commands were executed (a PRE-FLIGHT fail-fast, judgment-only).
Command Execution Policy: Non-Blocking Only
qa is non-interactive and headless. Every command it runs MUST return control to the shell when finished or be explicitly backgrounded. Foreground processes that occupy the agent shell indefinitely are forbidden.
| Pattern | Status | Example | |---------|--------|---------| | Command exits on completion | Allowed | bun test, curl http://localhost:8080/health | | Command backgrounded via tool | Allowed | run_in_background with emulator -avd Pixel_9 | | Command backgrounded in shell with output redirection | Allowed | emulator -avd Pixel_9 >/tmp/emulator.log 2>&1 & | | Bare blocking command | FORBIDDEN | emulator -avd Pixel_9 (hangs the shell indefinitely) |
Rule: if a command does not terminate on its own, it MUST be launched with run_in_background, OR with a trailing & AND output redirected to a file or /dev/null. A bare cmd & without redirection inherits stdout/stderr from the agent shell — the harness then waits on the inherited file descriptors until the backgrounded process exits, hanging the agent.
<Output_Format>
Output Format
## Cycle Summary | Phase | Status | Details | |-------|--------|---------| | PRE-FLIGHT | PASS / REQUEST_CHANGES | [MUST-NOT-DO / B⊆A result] | | BASELINE | PASS / FAIL | [build/test/lint summary] | | ADVERSARIAL E2E | PASS / FAIL | [matrix + scenario summary] | | Cycles run | N / max_cycles | [Same-Failure key if terminated early] | ## Scenarios Executed | # | source | actor | preconditions | steps | expected | result | why-needed | priority | |---|---|---|---|---|---|---|---|---| Every row's `source` is either `self-authored` or `caller-provided`. A `self-authored` row carries the six-field shape from [scenario-authoring.md] — actor · preconditions · steps · expected · why-needed · priority — filled in full; a `caller-provided` row carries whatever shape the caller supplied. This table is the canonical scenario record for the cycle: `result` maps to `Status` and `why-needed` maps to `Why-Needed` in the four-column working table [stage3-handson.md] mandates for hands-on execution — the column-count divergence is declared here, not fixed there. Close the table with exactly one coverage-delta line naming the impact-map domains from [scenario-authoring.md], which of those domains the rows above cover, and which are left uncovered. ## Verdict: [APPROVE / REQUEST_CHANGES / COMMENT] ## Issues (if any) [For each issue:] - **[CRITICAL/LOW]**: [Brief description] - Location: [file:line] - What: [problem] ## Evidence Files - [absolute path to each evidence file saved during this cycle] (Omit Evidence Files when no commands were executed — a PRE-FLIGHT fail-fast)
</Output_Format>
Approval Decision
- Issuance precondition. A
## Scenarios Executedsection is a precondition for issuing a verdict. When it is absent, the## Verdictheading is omitted rather than a verdict being issued — this reads as an unfinished cycle, never as a fourth value alongside the{APPROVE, REQUEST_CHANGES, COMMENT}domain below. The single exception to this absence rule is the PRE-FLIGHT fail-fast, which legitimately issues REQUEST_CHANGES with no cycle run and therefore no## Scenarios Executedsection. A section that is present with zero rows is a separate case, not an omission: when ADVERSARIAL E2E was skipped because the change is a genuinely inert refactor touching no risk surface (### ADVERSARIAL E2Eabove), the cycle is complete, its coverage-delta line states the change touched no risk surface, and a verdict is issued per the table below.
| Condition | Verdict | |-----------|---------| | PRE-FLIGHT contract violation | REQUEST_CHANGES (MUST-NOT-DO / B⊆A violated, cycle not executed) | | EXIT via max_cycles/Same-Failure-3x/Safety, unresolved | REQUEST_CHANGES (unresolved after cycle) | | CHECK passes (BASELINE + full matrix green) | APPROVE (or COMMENT to surface LOW notes — see On COMMENT below) |
Every issue surfaced MUST include a confidence score. See [feedback-protocol.md] for Confidence Scoring, Validation, and Conventional Comments.
On COMMENT. The decision above is binary — APPROVE or REQUEST_CHANGES. COMMENT is an optional soft-pass variant of APPROVE: emit it in place of APPROVE when there are LOW, non-blocking notes worth surfacing to the consumer. It carries no MEDIUM tier — severity is CRITICAL / LOW only — and it is never a partial or "almost" verdict. Consumers read COMMENT as approve-with-notes: a per-task verifier closes the task after its evidence gate; an objective-level completion gate still requires an explicit APPROVE (the never-false-complete invariant), so a COMMENT there prompts addressing the notes and re-verifying toward APPROVE, never completion.
Quick Reference
CYCLE: PRE-FLIGHT → PLAN → BASELINE → ADVERSARIAL E2E → CHECK → [DIAGNOSIS → FIX → RE-VERIFY loop ≤5] → EXIT → CLEANUP → ROLLBACK → STATE
PRE-FLIGHT: MUST-NOT-DO scope + B⊆A only; violation = immediate REQUEST_CHANGES, cycle NOT executed
BASELINE: build/test/lint green. See stage1-commands.md
MATRIX: 6 categories — failure paths, boundary/malformed input, injection, interruption, misleading success, idempotency. Breadth via scenario-authoring.md, depth via stage3-handson.md
DRIVERS: API→curl, Frontend→agent-browser (fallback playwright), Mobile→maestro, CLI→bash. No tmux.
LOOP: DIAGNOSIS→oracle (fresh, read-only) | FIX→sisyphus-junior (commits own scoped fix, never git commit -a) | RE-VERIFY→qa, full re-run, distrust fixer
EXIT: Goal Met / max_cycles=5 / Same-Failure-3x (scenario-id+root-cause-file+root-cause-symbol) / Safety
ROLLBACK: git revert fix_head_before..HEAD only, NEVER git reset --hard; 3 guards: linear-descendant, non-empty-range=ERROR, post-revert disjointness on user_dirty_set; REFUSE the cycle on user_dirty_set overlap; rm -rf/force auto-deny honored
STATE: bun ${CLAUDE_SKILL_DIR}/scripts/qa-state.ts <sub>; continue resumes at last phase/cycle
NESTING: qa's fix-loop must NOT be called inside another fix-loop (e.g. goal) — doc contract, YAGNI; upgrade trigger: add a code guard when qa gains its first fix-loop-owning caller
ROSTER: ## Scenarios Executed is a precondition for verdict issuance; absent → verdict not issued, cycle incomplete. Exception: PRE-FLIGHT fail-fast issues REQUEST_CHANGES with no roster — never synthesize an empty one there; present+0 rows means inert refactor, a completed cycle
FEEDBACK: feedback-protocol.md for Confidence Scoring; CONFIDENCE 0-49 discard, 50-79 nitpick, 80+ report