sdlc-testrun
Invoked explicitly by /run-test. Implements and executes the acceptance and live-integration tests from the test case catalog, records every result and the run detail into state, and triages each fail
SKILL.md.claude/skills/sdlc-testrun/SKILL.md
GitHub/run-test — acceptance execution
Protocol (shared — do not restate here)
Framework assets — .agent/framework.yaml, .agent/steps/, .agent/rules/,
.agent/gates/, .agent/templates/, .agent/modules/ — resolve project-first: use
the repository's copy when it exists, otherwise ${CLAUDE_PLUGIN_ROOT}/.agent/…. That is
how one repo can override a single template or rule without forking the framework.
.agent/project-context.yaml and .agent/state/ are always project-local — never
read or write them under the plugin root.
- If
.agent/state/context-cache.mdexists and itsframework_versionmatches.agent/framework.yaml, Read ONLY that file. Otherwise Read.agent/steps/context-loader.mdand execute it, then write the digest back to the cache. - Read
.agent/steps/gate.mdand execute it with the phase inputs below. - On completion, format output per
.agent/steps/report-footer.md. Never copy the contents of those files into this skill.
Phase inputs — phase acceptance, gate G5b, inputs the catalog + the in-scope
.feature files, outputs under {{layout.acceptance_tests}}, plus
.agent/state/runs/{ISO8601}.json and a run report under {{paths.review_dir}}.
Step 1 — Select the rows
Only catalog rows with Level in {acceptance, integration-live}. eval rows are
/eval's; unit rows were /unittest's.
Gate Step 2.7 has already run .agent/steps/input-check.md against
input_contracts.acceptance. NO_SOURCE or NOT_READY → report and stop.
If G5a_unit has not passed, say so and recommend running /unittest first: the slow,
expensive suite should not be spent rediscovering what the fast one already knows. But
this is advice, not a refusal — QA may legitimately need to execute acceptance cases
against a build whose unit suite is red, to find out whether the failure is broader than
one module. Record G5a: {verdict} in the report so the result is read in context.
Step 2 — Implement the acceptance tests
One test per row, under {{layout.acceptance_tests}}, named
test_{UC-ID} per the profile's conventions. Where the stack has a BDD runner, bind
directly to the .feature scenarios so the specification is executed rather than
transcribed — a transcription drifts from its source the first time either is edited.
Tag with @trace.verifies and @trace.test_type=acceptance, and mark them so the
default fast suite excludes them.
Acceptance tests exercise the system through its real entry points, with real
adapters where the row says integration-live. Reaching inside to set up state is
acceptable for arrangement; asserting on internals is not — that is a unit test in
disguise.
Step 3 — Execute
Run quality_commands.acceptance. Capture per row: pass, fail, or blocked
(could not run — environment missing, dependency unavailable, precondition
unreachable).
Write the raw detail to .agent/state/runs/{ISO8601}.json: command, exit code, duration,
per-test outcome, and the failure output. This file is append-only history; keep it out
of the small state files that get read on every command.
Step 4 — Triage every failure
No failure may be recorded without a diagnosis. An unclassified failure cannot be
routed, so it stalls the loop. Classify against framework.yaml → loop_routes:
| Observation | Diagnosis | route_to |
|---|---|---|
| Spec clear, system behaves differently | impl_defect |
code |
| The test encodes the wrong expectation | test_defect |
tests |
| Scenario ambiguous or untestable as written | spec_gap |
srs |
| Requirement conflicts with the PRD or is new | product_gap |
prd |
| An NFR cannot be met by this design | design_gap |
techdoc |
| Passes and fails across identical runs | flaky | tests |
Bias: when a test fails, the code is wrong. Routing to tests or srs requires
stating why the test or scenario is the thing that is wrong, and leaves a change-log
entry in the SRS.
A blocked row is never silently acceptable — it needs a finding too, or the gate reads
an unrun test as an absence of problems.
Flakes get a finding of their own. A test that passes on retry has told you something about the system or the test; suppressing it with a retry loop discards that.
Step 5 — Write findings and the run report
Append one row per failure to {{paths.findings}}, with id = RF-{FEAT-ID}-{nnn},
severity, category, found_in: acceptance, and the route_to from triage.
Write the run report to {{paths.review_dir}}/{FEAT-ID}/TR-{nnn}-acceptance.md:
command, environment, totals, the per-row table, and the triage summary.
Step 6 — Evaluate G5b and record
Run .agent/gates/G5b-acceptance.yaml. For AI features it also requires a recorded eval
run for every touched prompt version, pass rate at or above the spec threshold, no
regression beyond tolerance versus baseline, and cost and latency within budget. Those
figures come from /eval — if it has not run, this gate is block, not pass.
Gates read the recorded eval state rather than triggering a run, so checking a gate never
costs money.
Update state: phase: acceptance, the verdict and input_hash, the acceptance counts in
quality.json, and the findings counts on the feature.
Boundaries
- Do not modify implementation code. Diagnose and route;
/gen-codefixes. - Do not retry a failing test until it passes and call it green.
- Do not mark a row
blockedto avoid a failure — blocked means genuinely could not run. - Do not run the eval suite here.
/evalowns it, so that a run's cost is always a deliberate command rather than a side effect. - Do not weaken a threshold to pass a gate.