Use when the user wants end-to-end QA testing for a feature, branch, or PR — scans the project for entry points, generates a TEST_PLAN.md with edge cases, launches the service in an isolated container, and dispatches test runners (cheap agy for simple HTTP cases, claude for complex flows). Profile-driven: `init` once, then `run` repeatedly.
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Use when the user wants end-to-end QA testing for a feature, branch, or PR — scans the project for entry points, generates a TEST_PLAN.md with edge cases, launches the service in an isolated container, and dispatches test runners (cheap agy for simple HTTP cases, claude for complex flows). Profile-driven: `init` once, then `run` repeatedly.
x-qa — Profile-Driven E2E QA Orchestrator
Input Contract
run accepts a free-form {{ARGUMENTS}} — empty string, PR #<n>, an
entry-point name, a file path, a directory, or prose describing a feature.
The bootstrap classifier (Run Phase 3) resolves it. Explicit flags
(--pr/--branch/--service/--plan) override classification. Do NOT
add new user-facing flags for input source — the classifier handles it.
Bootstrap (MANDATORY)
Before any subcommand:
Pin capabilities per ../x-shared/capability-loading.md.
Read ../x-omo/SKILL.md if agy_cli or plugin.omc capability is pinned (needed for fanout).
Pin runner pair (D10) — derive X_QA_SIMPLE_RUNNER and X_QA_COMPLEX_RUNNER from the Capability Routing table below and export them for downstream phases. The runner template in references/case-runner-prompts.md reads these env vars rather than hardcoding a tool name.
Also pin X_QA_EXPLORE_MODE (team when team orchestration / plugin.omc is pinned, else bg-fanout) and X_QA_EXPLORER (the exploratory worker subagent) per the Exploratory Team Routing table.
For run only (D9) — invoke scripts/doctor.sh first. Refuse if the doctor fails. Skippable via --skip-doctor.
run flags: --worktree <path>, --service <name>, --channel <name>, --plan <path>, --no-launch, --max-bg <N> (default 8), --no-bg (force synchronous dispatch), --staleness-days <N> (default 7), --retry-flaky <N> (default 2), --allow-flaky-rate <pct>, --verdict-only, --skip-doctor, --no-profile, --pr <num>, --branch <name>, --no-kb (skip corpus + baseline + auto-promote), --kb-promote-after <N>, --kb-disable-auto-promote, --allow-coverage-gaps (downgrade the coverage gate to a warning), --no-explore (skip the exploratory bug-hunt) / --explore (force it even in CI)
KB Subcommands
The KB (.x-skills/x-qa/kb/) is the team-shared, git-tracked layer that
makes future runs faster and lets teams reuse proven test scripts.
Full schema: references/kb-schema.md. Curation rules:
references/kb-curation.md.
Form
Purpose
/x-skills:x-qa kb list [--cases|--flows|--baselines]
Run the judge over kb/evals/gold/<id>.jsonl, compute Cohen's κ, write kb/evals/calibration/<id>.json. Required before a judge can hard-fail a run. (Routes to calibrate-judge.sh --gold <repo>/.x-skills/x-qa/kb/evals/gold/<id>.jsonl --rubric-id <id> --out-dir <repo>/.x-skills/x-qa/kb/evals/calibration/ [--threshold 0.8].)
Cross-team sharing is the git-tracked KB itself (or a submodule when shared
across repos). Tarball export/import was cut as redundant with git.
Capability Routing
Pinned
Simple Runner
Complex Runner
agy_cli + plugin.omc
x-gemini (agy) serial
Agent oh-my-claudecode:qa-tester (sonnet) bg
agy_cli only
x-gemini (agy) serial
Agent Explore (sonnet) bg
plugin.omc only
Agent oh-my-claudecode:executor (haiku) bg
Agent oh-my-claudecode:qa-tester (sonnet) bg
neither
Agent Explore (haiku) bg
Agent Explore (sonnet) bg
The bootstrap step pins the runner pair into env vars X_QA_SIMPLE_RUNNER and X_QA_COMPLEX_RUNNER (read by case-runner-prompts.md). Never hardcode an Agent subagent_type in the runner template — always reference the pinned env var.
Serial-agy rule (MANDATORY). When X_QA_SIMPLE_RUNNER is the agy-agent (x-gemini) runner, simple-runner cases MUST be dispatched one at a time — effective --max-bg 1 for the agy runner — because concurrent agy processes hang (agy is a process-global singleton). The parallel-wave fanout (--max-bg) stays in effect for the non-agy fallback simple runners (OMC executor / Explore) and for all complex runners (qa-tester / Explore). The cap is on concurrent agy processes (≤1), not on the wave: complex cases and non-agy simple cases in the same wave still run in parallel; only the agy simple cases serialize among themselves.
Exploratory Team Routing (Arc C)
Pinned
X_QA_EXPLORE_MODE
X_QA_EXPLORER
team orchestration (plugin.omc)
team (shared bug-board)
oh-my-claudecode:qa-tester (sonnet)
otherwise
bg-fanout (background Agent)
Explore (sonnet)
Bootstrap pins X_QA_EXPLORE_MODE and X_QA_EXPLORER for Phase 13.5. See
references/explore-team.md (mode gate, bounded swarm) and
references/explorer-prompts.md (worker prompt).
Run Envelope (machine-readable, stable)
Success:
✓ x-qa run complete
QA_VERDICT=pass|warn|fail
QA_VERDICT_REASON=<one-line> # first blocking failure, or first warning, or "all gates passed"
QA_TOTAL=<n>
QA_PASSED=<n>
QA_FAILED=<n>
QA_FLAKY=<n>
QA_FLAKY_RATE=<float>
QA_REPORT=<abs path>
QA_PLAN=<abs path>
QA_RUN_ID=<id>
DURATION_S=<float>
ENTRY_POINT=<name>
SERVICE_LAUNCHED=true|false
KB_REUSED=<n> # cases pulled from corpus (not regenerated)
KB_GENERATED=<n> # cases minted this run
KB_PROMOTED=<n> # cases auto-promoted to corpus this run
KB_PROMOTE_STATUS=ok|disabled|error
COVERAGE_REQUIRED=<n> # required obligations from scope.json
COVERAGE_COVERED=<n> # required obligations satisfied by a case
COVERAGE_UNCOVERED=<csv> # uncovered required obligation ids ("" when none)
EXPLORE_RAN=true|false # false when skipped (CI / --no-explore / no service)
EXPLORE_FINDINGS=<n> # unique suspected findings on the bug-board
EXPLORE_CONFIRMED=<n> # findings that survived triage
EXPLORE_CASES_MINTED=<n> # confirmed findings minted into kb cases
EXPLORE_OBLIGATIONS_ADDED=<n> # novel obligations minted from "none" findings
CHANNELS_TESTED=<csv> # channels selected for execution (names)
CHANNELS_SKIPPED=<name:reason,...> # skipped channels + reason (stateful-not-owned / stateful-unverifiable / stateful-owned-chat-driver-deferred)
QA_VERDICT is ternary as of v2. warn = non-blocking gates failed, no blocking ones did. Consumers branching on pass|fail should treat warn as pass for back-compat OR opt into ternary semantics via QA_VERDICT_REASON.
Failure:
✗ x-qa run FAILED
REASON=<one-line>
PHASE=<bootstrap|doctor|plan|launch|dispatch|collect|aggregate>
QA_RUN_ID=<id-if-allocated>
doctor: references/doctor-checks.md + scripts/doctor.sh (validates both profile and kb/index.json)
generate: references/test-plan-schema.md (LLM-generated inline; scripts/plan-generate.sh is a v2 placeholder, not shipped in v1). Planner MUST consult kb/cases/ + kb/flows/ first per the corpus contract in references/test-plan-schema.md § Corpus Reuse.
run: see "Run Phases" below; intent via references/intent-detection.md; scout via references/scout-prompt.md; KB layer via references/kb-schema.md + references/kb-curation.md
kb <subcmd>: dispatched to scripts/kb-<subcmd>.sh. No LLM in the loop — these are pure-shell maintenance commands.
Run Phases
Bootstrap (above).
Memory recall (only when mcp.basic_memory pinned in bootstrap-active set): one mcp__basic-memory__search_notes({ query: "<test path or framework + 'flake'>", page_size: 10 }) call. Surface prior flake notes as test-history context for case classification — not autopilot. Apply consumer rules from ../x-shared/mcp-toolbox.md § Consumer rules. When mcp.basic_memory is not pinned, skip silently — Claude's native auto-memory file still applies.
Auto-doctor (skippable via --skip-doctor).
Classify intent. Run scripts/classify-intent.sh "{{ARGUMENTS}}", persist to <run-dir>/intent.json. If confidence == "low" OR multiple candidates surface, ask the user ONE question per references/intent-detection.md § Ask-When-Ambiguous, then rewrite intent.json with confidence: high.
Resolve target from intent: service → entry name; branch/pr → PR-surface derivation (references/pr-surface-derivation.md); spec/artifact/artifact-dir/prose → trigger Phase 5 (Scout). Refuse if resolved entry's type != http (v1 limitation).
Channel resolution. If intent.json.resolved.channel (or --channel)
is set, resolve it against profile.json.channels[] and pin
X_QA_CHANNEL + X_QA_DRIVER. Read the driver's feature-gate per
references/channel-drivers.md:
http → execute (Phases 8–15 as today, against the channel's base_url).
browser / computer-use → if the gating capability is absent, emit
CHANNEL_SKIPPED=<name> reason=driver '<driver>' not executable and stop
with a clear notice (capture-only in this release). Do NOT fall back to a
different channel silently.
Stateless-first default (no --channel). Run scripts/lib/channel-select.sh --profile <profile> --worktree <worktree> [--channel <name>] → persist
<run-dir>/channels.json. With no --channel, it defaults to stateless
channels (singleton_id == null) on the primary entry point. No channels[]
at all → the implicit primary http channel (back-compat).
Stateful resolution (ownership from feature-overrides.local.json only, R2):
owned here AND driver == http → EXECUTE via the existing http runner path.
owned here AND driver ∈ {browser, computer-use} → skip
CHANNEL_SKIPPED reason=stateful-owned-chat-driver-deferred.
not owned (the default) → skip CHANNEL_SKIPPED reason=stateful-not-owned.
isolate not set up → skip CHANNEL_SKIPPED reason=stateful-unverifiable
(never test a stateful channel blind).
channels.json.tested drives Phases 8-15; channels.json.skipped feeds the
envelope's CHANNELS_SKIPPED.
Scout (conditional). Only when intent ∈ {spec, artifact, artifact-dir, prose}: dispatch $X_QA_SIMPLE_RUNNER inline per references/scout-prompt.md. Persist <run-dir>/scope.json. On invalid JSON / timeout, use whole-profile coverage and warn. The scout also performs Domain Research (references/scout-prompt.md § Domain Research) — code-first modeling of entities/constraints/invariants/transitions — and emits domain_model + obligations[] into scope.json. When intent is not scout-eligible (branch/pr/service), obligations[] is absent and Phase 7.5 is a no-op.
KB consult (skipped on --no-kb). Read kb/index.json. For every endpoint in scope (from scout or PR-surface), collect matching kb/cases/*.yaml (not quarantined) and kb/flows/*.yaml. Pass this corpus to the planner.
6.5. Gap analyze (skipped on --no-kb). Run scripts/gap-analyze.sh --scope-file <run-dir>/scope.json when scout produced a scope (otherwise run without --scope-file and analyze the full index). Persist to <run-dir>/coverage_gaps.json. Inject as a ## Coverage Gaps block in the planner prompt per references/gap-analyzer.md. Default --staleness-days 7; override via profile.json.gap_analyzer.staleness_days.
Plan: read --plan <path> if given, else generate per references/test-plan-schema.md § Corpus Reuse using profile catalog + scope.json + KB corpus as ground truth. The planner SHALL prefer corpus IDs over fresh generation when the endpoint+category already has a green case. Cases requiring auth inherit profile.json.auth_case_id as a default precondition_case_id per references/kb-schema.md § precondition_case_id and references/case-runner-prompts.md § Precondition Chaining.
7.5. Coverage gate (skipped when scope.json has no obligations[], or on --allow-coverage-gaps). Run scripts/coverage-check.sh --scope <run-dir>/scope.json --plan <plan>. If verdict == fail, refuse the plan with PHASE=plan and REASON=uncovered required obligations: <ids> — the planner must add cases for the named obligations and re-emit. --allow-coverage-gaps downgrades the refusal to a warning (uncovered ids surfaced in QA_REPORT.md notes). Fold COVERAGE_REQUIRED / COVERAGE_COVERED / COVERAGE_UNCOVERED into the run envelope.
Launch service via scripts/launch-entry-point.sh (skipped on --no-launch or --service <ext-url>).
Health wait via scripts/health-wait.sh.
Classify cases per references/classification-rules.md (simple vs complex).
Compute dispatch waves. Pipe plan JSON through scripts/lib/topo-order.sh. Refuse plan on cycle (exit 2) or unknown dependency id (exit 1). Each wave dispatches in parallel (capped at --max-bg); the next wave starts when every case in the current wave reaches terminal state. Exception — serial agy: if X_QA_SIMPLE_RUNNER is the agy-agent runner, the simple cases in a wave are dispatched one at a time (effective --max-bg 1 for the agy runner — concurrent agy processes hang); see "Serial-agy rule" under Capability Routing. Non-agy simple runners and the complex runner keep the parallel --max-bg fanout. Cases whose deps failed are marked skipped, not fail. Templates in references/case-runner-prompts.md. Each case's full precondition chain is pre-resolved by scripts/run/resolve-preconditions.sh (depth cap 4) into X_QA_PRECONDITION_STEPS before dispatch.
Background-execution gate (CI vs local). Dispatch is run_in_background: true only when the environment looks interactive ([[ -z "$CI" && -z "$GITHUB_ACTIONS" && -z "$BUILDKITE" && -z "$GITLAB_CI" ]]). In CI, dispatch synchronously so the runner's stdout/stderr land in the build log and the run does not complete before agents finish. The --no-bg flag forces synchronous dispatch unconditionally.
Collect every dispatch terminal state (mandatory per ~/.claude/rules/background-agents.md). Never background_cancel(all=true) before collection.
Retry flaky inline up to --retry-flaky.
13.5. Exploratory bug-hunt (team) — default on a local run; skipped in CI; --no-explore opts out, --explore forces it. Gate on the Phase-11 CI predicate. Requires the service to be up (skip if Phase 8 was skipped). When scope.json.obligations[] is present, partition it into ≤6 clusters (scripts/explore/cluster-partition.sh --max-workers 6); otherwise cluster by reachable endpoints (skip if neither). Dispatch one Exploratory Worker per cluster (references/explorer-prompts.md) via X_QA_EXPLORE_MODE (native team + shared bug-board, or background fanout — references/explore-team.md), each bounded to a ≤15-probe budget, writing to <run-dir>/explore/board.jsonl. Then dedup by signature (scripts/explore/finding-merge.sh), triage each unique finding independently (references/triage-verify.md), and mint a red repro stub per confirmed finding (scripts/explore/finding-to-case.sh) for the x-bugfix route + the report. Do NOT KB-promote these — the KB is the green corpus and Phase 16 auto-promotes only green cases; a repro stub becomes a regression case only after the fix lands and it goes green (via the existing auto-promote path). Count EXPLORE_CONFIRMED from the triaged set (this step), NOT from finding-merge.sh output (which runs pre-triage, when every finding is still suspected). Fold EXPLORE_* counters into the envelope.
Teardown via launch entry's launch.teardown (skipped if Phase 8 was skipped).
Aggregate via scripts/aggregate-results.sh → QA_REPORT.md. Propagate scope.json.open_questions into the report's notes section. KB write-back (skipped on --no-kb): update kb/baselines/<endpoint>.json for every case, append a run summary line to kb/.ledger.jsonl, compute drift signals.
KB auto-promote (skipped on --no-kb or --kb-disable-auto-promote). Invoke scripts/kb-promote.sh --auto. Emits KB_PROMOTED= / KB_DEMOTED= for the envelope.
Emit envelope.
Real-QA Contract (MANDATORY)
run tests the system the way a QA engineer drives it —
it never executes the repository's own test suites. The runner MUST NOT
invoke npm test,
npm run test:e2e, pytest, playwright test, cypress run, go test,
vitest, or any project test command. Instead it drives the actual channel:
issue real requests (curl for http), adjust fixture/mock data, and mint cases
from QA_MEMORY.md + the KB corpus. launch.command starts the service only
(references/service-launch.md); it is never a test command. This holds across
every driver.
Eval class (v1). LLM/agentic features are tested with eval scorers (llm-rubric,
semantic-similarity) — x-qa scores outputs itself via the native judge-runner
(scripts/evals/score-case.sh); it MUST NOT shell out to external eval frameworks
(deepeval, promptfoo) any more than it runs the repo's test suites. The judge model
SHOULD differ from the model behind the system-under-test (operator responsibility — not
machine-enforced in v1; x-qa cannot infer the SUT's model). A judge may set QA_VERDICT=fail
only when validated against a human gold set (κ ≥ 0.90); otherwise it is advisory (warn). See
references/eval-scorers.md.
After This Skill
If invoked by x-team: emit envelope only, x-team consumes via Skill return value.
If invoked by user: print envelope + path to QA_REPORT.md + summary table.
On fail: surface offer to route into /x-skills:x-bugfix with the failed cases as input.
Confirmed exploratory findings (EXPLORE_CONFIRMED > 0) are also offered to /x-skills:x-bugfix, each carrying its minted case as the reproduction.
Persist test pattern (only when mcp.basic_memory pinned): apply the § Memory Reflex durability gate first (a reusable test pattern or real flake root cause, not a routine run summary), then one mcp__basic-memory__write_note({ title: "<pattern-kind>: <one-line>", directory: "lessons/<project-slug>", content: "<test pattern or flake observation>", tags: ["<project-slug>", "x-qa", "<framework>", "test-plan"] }) call (durability gate, placement, and Update over duplicate — same-kind only — per ../x-shared/mcp-toolbox.md § Memory Reflex / § Consumer rules). Skip silently when not pinned.