test-plan-review
Reviews a generated test plan for completeness, consistency, and quality using a 5-criteria rubric. Scores, auto-revises, and re-scores (max 2 cycles). Use for automated quality assessment and iterati
SKILL.mdskills/test-plan-review/SKILL.md
GitHubTest Plan Reviewer
Internal orchestrator: scores five criteria (0–2 each), then auto-revises and re-scores failing plans up to twice.
Usage
This skill is not user-invocable. It is called by:
test-plan.create(Step 4)- automation/orchestrator flows that need score + auto-revision behavior
Inputs
From arguments
Parse $ARGUMENTS to extract:
- Feature directory (required): path to directory containing
TestPlan.md
Auto-detection
If test-plan.create just generated a plan in this session and no arguments were provided, use its feature directory.
Process
Model selection for review forks
For every score, review, and revise Agent call, including repeated cycles,
omit the model parameter so normal runtime resolution uses the active model
for this review skill. Never pass a per-call inherit value, model ID, or
family alias as a model override.
Step 0: Python dependencies
Install the test-plan package (scripts are then importable anywhere):
bash "${CLAUDE_SKILL_DIR}/../../scripts/bootstrap.sh" --layout "${CLAUDE_SKILL_DIR}" || exit 1
(cd "$(cd -P "${CLAUDE_SKILL_DIR}/../.." && pwd -P)" && uv sync --extra dev)
If installation fails, inform the user and do NOT proceed.
Step 1: Read Test Plan and Resolve Source Strategy
Read
<feature_dir>/TestPlan.mdRead frontmatter to extract
source_key:source_key=$(cd "$(cd -P "${CLAUDE_SKILL_DIR}/../.." && pwd -P)" && \ uv run python scripts/frontmatter.py read <feature_dir>/TestPlan.md source_key)Resolve the strategy with the shared snapshot-primary resolver: use
<feature_dir>/.source-strategy.mdif saved bytest-plan.create; otherwise fetch from Jira and save it. Fail if neither source is available.repo_root=$(cd -P "${CLAUDE_SKILL_DIR}/../.." && pwd -P) resolve_result=$(cd "$repo_root" && uv run python scripts/resolve_strategy.py <feature_dir> "$source_key") resolve_exit=$? if [ "$resolve_exit" -ne 0 ]; then echo "ERROR: scripts/resolve_strategy.py failed to resolve the source strategy — stopping review." >&2 echo "$resolve_result" >&2 exit 1 fi strategy_file_path=$(printf '%s\n' "$resolve_result" | jq -r '.strategy_file')Jira access is read-only. The resolver preserves typed request failures and returns stable
jira_fetch_failedwithout exposing request URLs/server details or accepting partial strategies.Keep
strategy_file_pathas a persistent local snapshot: never remove it, including at Step 5 or between cycles; reuse it unchanged for every Step 4e re-score.Compute interface coverage, AC/NFR citation validity, bidirectional scope coverage, and actionability evidence deterministically (not as LLM judgments).
scripts/build_citation_inputs.pyderivesac_count/nfr_categoriesfromstrategy_file_pathand calls the validators:gate_result=$(cd "$repo_root" && uv run python scripts/build_citation_inputs.py <feature_dir> --strategy-file "$strategy_file_path") || { echo "ERROR: scripts/build_citation_inputs.py failed to construct citation gate inputs — stopping review." >&2 echo "$gate_result" >&2 exit 1 } interface_coverage_result=$(printf '%s\n' "$gate_result" | jq -c '.interface_coverage_result') ac_citations_result=$(printf '%s\n' "$gate_result" | jq -c '.ac_citations_result') ac_coverage_result=$(printf '%s\n' "$gate_result" | jq -c '.ac_coverage_result') scope_coverage_result=$(printf '%s\n' "$gate_result" | jq -c '.scope_coverage_result') actionability_result=$(printf '%s\n' "$gate_result" | jq -c '.actionability_result')On nonzero exit, stop: gate-input construction failed (for example, unreadable strategy or parser bug), which is an execution failure, not plan data. Never fall back to degraded mode. Before create-cases,
valid: trueis expected: blank Section 9.2 Test Cases and unpopulated Section 6.2 are skipped. Once Section 6.2 is populated, each populated row needs aTC-E2E-*orTC-UI-*reference.missing_e2e_or_ui_in_6_2identifies declared non-pending interfaces with populated rows lacking either reference;missing_in_6_2identifies absent, blank, or placeholder rows. Actionabilityvalidcovers only blockingbare_tbd/missing_details;advisory_gapsrecords missing/vague versions and incomplete test-data examples. Pass both to the score agent. Section 3.1 needs substantive environment/configuration evidence; headings and vague/unavailable-only statements block. The same occurrence-level TBD classifier applies to Sections 3.1–3.3: groundedTBD — Resolution: ..., includingderivefrom a named overlay, is allowed; bare/unresolved TBD blocks. RBAC needs a role, permissions, and concrete resource (all/any/everycollections and wildcard resources are not concrete). Count examples only in explicit example labels/table columns ore.g./for exampleclauses.Have Python read TestPlan.md frontmatter, validate
additional_docspaths, and read those files; never trust the LLM to resolve paths:additional_docs_raw=$(cd "$repo_root" && uv run python scripts/resolve_additional_docs.py <feature_dir>) || { echo "ERROR: scripts/resolve_additional_docs.py failed — stopping review." >&2 echo "$additional_docs_raw" >&2 exit 1 } additional_docs_result=$(printf '%s\n' "$additional_docs_raw" | jq -c '.docs')Run deterministic scope (Section 2.1) and boilerplate (Sections 1.3/2.3/8) checks; results feed SCOPE FIDELITY and SPECIFICITY without an LLM call. Resolve team pattern overrides from TestPlan.md
components, mapped toCOMPONENT_TEST_DIR_MAPnames byget_component_test_dir.py --teams-only:team_list=$(cd "$repo_root" && uv run python scripts/get_component_test_dir.py --teams-only <feature_dir>) || { echo "ERROR: scripts/get_component_test_dir.py --teams-only failed — stopping review." >&2 echo "$team_list" >&2 exit 1 } scope_check_result=$(cd "$repo_root" && uv run python scripts/validate_test_scope.py <feature_dir>/TestPlan.md \ --include-teams="$team_list" --checks-dir=scripts/checks) || { echo "ERROR: scripts/validate_test_scope.py failed — stopping review." >&2 echo "$scope_check_result" >&2 exit 1 } boilerplate_result=$(cd "$repo_root" && uv run python scripts/detect_boilerplate.py <feature_dir>/TestPlan.md \ --include-teams="$team_list" --checks-dir=scripts/checks) || { echo "ERROR: scripts/detect_boilerplate.py failed — stopping review." >&2 echo "$boilerplate_result" >&2 exit 1 }validate_test_scope.py/detect_boilerplate.pyreturn JSON and always exit 0. Nonzero means a script failure (such as bad path/config); stop as for other Step 1 execution failures.
Step 2: Score (fork)
Load calibration examples and stop on nonzero exit. Add pairs under calibration/core/, optional
calibration/ui/, or calibration/<team>/; do not edit this skill for examples.
calibration_raw=$(cd "$repo_root" && uv run python scripts/load_calibration.py \
"${CLAUDE_SKILL_DIR}/calibration" --include-teams="$team_list") || {
echo "ERROR: scripts/load_calibration.py failed — stopping review." >&2
echo "$calibration_raw" >&2
exit 1
}
calibration_text=$(printf '%s\n' "$calibration_raw" | jq -r '.calibration_text')
printf '%s\n' "$calibration_raw" | jq -r '.warnings[]?' >&2
Read the score agent prompt from ${CLAUDE_SKILL_DIR}/prompts/score-agent.md.
Launch a forked score agent with these substitutions and omit the Agent
model parameter so normal runtime resolution uses this review skill's active
model:
{FEATURE_DIR}= feature directory path{TEST_PLAN_PATH}=<feature_dir>/TestPlan.md{STRATEGY_FILE_PATH}=strategy_file_pathfrom Step 1{CALIBRATION_TEXT}=calibration_textfromload_calibration.pyabove{INTERFACE_COVERAGE_RESULT}= JSON from Step 1 (interface_coverage_result){AC_CITATIONS_RESULT}= JSON from Step 1 (ac_citations_result){AC_COVERAGE_RESULT}= JSON from Step 1 (ac_coverage_result){SCOPE_COVERAGE_RESULT}= JSON from Step 1 (scope_coverage_result){ACTIONABILITY_RESULT}= JSON from Step 1 (actionability_result){ADDITIONAL_DOCS_CONTENT}= JSON from Step 1 (additional_docs_result){SCOPE_CHECK_RESULT}= JSON from Step 1 (scope_check_result){BOILERPLATE_RESULT}= JSON from Step 1 (boilerplate_result)
The score agent returns rubric scores and a grounding cross-reference table.
Completeness checks performed by the score agent:
| Section | Check |
|---|---|
| 1.1 Purpose | Does it clearly state what is being tested and why? |
| 1.2 Scope | Are in-scope and out-of-scope explicitly defined? |
| 1.3 Test Objectives | Is there at least one objective per STRAT acceptance criterion (every AC covered), plus grounded NFR objectives where applicable? |
| 2.1 Test Levels | Are the selected levels appropriate for the feature type? |
| 2.3 Priorities | Are P0/P1/P2 definitions specific to this feature, not generic? |
| 3.1 Cluster Config | Is substantive environment/configuration evidence present, with versions/dependencies specified or unknowns recorded with an explicit resolution path? Vague/unavailable-only content and bare/unresolved TBDs are blocking; missing/vague versions remain advisory when the section is otherwise substantive. |
| 3.2 Test Data | Are test-data requirements concrete enough to act on, with incomplete format/examples retained as advisory gaps? |
| 4 Interfaces Under Test | Are entries grounded in source documents, not fabricated? |
| 6.1 E2E Scenarios | Is the E2E Scenario Summary populated with TC-E2E-* entries? (Note: expected to be empty until create-cases runs) |
| 6.2 E2E Coverage | Does each non-pending interface from Section 4 have at least one TC-E2E-* or TC-UI-* reference in each populated Section 6.2 row? Checked deterministically via interface-coverage (Step 1), not LLM table-reading. (Note: expected to be empty until create-cases runs) |
| 7.1 Disconnected | Addressed with testing considerations or explicitly marked Not Applicable with justification? |
| 7.2 Upgrade | Addressed with testing considerations or explicitly marked Not Applicable with justification? |
| 7.3 Performance | Addressed with testing considerations or explicitly marked Not Applicable with justification? |
| 7.4 RBAC | Addressed with testing considerations or explicitly marked Not Applicable with justification? |
| 7.5 Security | Addressed with testing considerations or explicitly marked Not Applicable with justification? |
| 8 Risks | Are risks specific to this feature, not boilerplate? |
| 9 Environment | Is there enough detail to set up a test environment? |
Step 3: Review (fork)
Read the review agent prompt from ${CLAUDE_SKILL_DIR}/prompts/review-agent.md.
Launch a forked review agent with these substitutions and omit the Agent
model parameter so normal runtime resolution uses this review skill's active
model:
{CLAUDE_SKILL_DIR}=${CLAUDE_SKILL_DIR}(the selected review skill directory){FEATURE_DIR}= feature directory path{ASSESSMENT_TEXT}= full output from the score agent (Step 2){FIRST_PASS}=true(first assessment cycle)
The review agent writes rubric scores, feedback, and validated frontmatter to
<feature_dir>/TestPlanReview.md.
Consistency checks performed by the review agent:
- Do the interfaces in Section 4 align with the scope in Section 1.2?
- Do the test levels in Section 2.1 match the interface types in Section 4?
- Are priority assignments in Section 6.1 consistent with the definitions in Section 2.3?
- Does Section 9.2 list all interfaces from Section 4? (deterministic — from the
interface-coverageresult computed in Step 1, not re-derived) - Are NFR categories in Section 7 consistent with the feature scope? (e.g., a feature that pulls images should not mark Disconnected as N/A)
- Does Section 6.2 E2E Coverage Matrix include all non-pending interfaces from Section 4 and at least one
TC-E2E-*orTC-UI-*reference per populated interface row? (deterministic — frommissing_in_6_2andmissing_e2e_or_ui_in_6_2in theinterface-coverageresult; expected unpopulated until create-cases runs)
Step 3.5: Enforce Citation Gate
Reapply Scope Fidelity/Specificity caps and the Actionability correction deterministically.
enforce_citation_gate.py always exits 0 and reports JSON: blocking evidence caps Actionability
above 1 to 1/2; valid evidence never raises 0/1. actionability_capped is true only if the cap
changes the score.
repo_root=$(cd -P "${CLAUDE_SKILL_DIR}/../.." && pwd -P)
gate_result=$(cd "$repo_root" && uv run python scripts/enforce_citation_gate.py <feature_dir> \
--ac-citations-result "$ac_citations_result" --ac-coverage-result "$ac_coverage_result" \
--scope-check-result "$scope_check_result" --boilerplate-result "$boilerplate_result" \
--scope-coverage-result "$scope_coverage_result" --actionability-result "$actionability_result")
gate_status=$(printf '%s\n' "$gate_result" | jq -r '.status')
case "$gate_status" in
overridden|ok|skip) ;;
*)
echo "ERROR: scripts/enforce_citation_gate.py failed — stopping review." >&2
echo "$gate_result" >&2
exit 1
;;
esac
If overridden, Step 4 uses corrected scores/feedback, not the agent's numbers. Any status besides
overridden/ok/skip means gate failure; stop.
Step 4: Check Criteria and Revise (max 2 cycles)
After the review agent completes, read the review frontmatter:
(cd "$(cd -P "${CLAUDE_SKILL_DIR}/../.." && pwd -P)" && uv run python scripts/frontmatter.py read <feature_dir>/TestPlanReview.md)
If all five scores.* are 2, proceed to Step 5. The result can be Ready with non-empty
actionability_result.advisory_gaps; these are visible follow-ups, not revision failures.
If any scores.* is < 2, revise. Missing/vague OpenShift/RHOAI versions or incomplete test-data
format/examples in advisory_gaps alone do not trigger revision.
Revision Loop
Initialize cycle counter: reassess_cycle=0
4a. Filter for revision:
(cd "$(cd -P "${CLAUDE_SKILL_DIR}/../.." && pwd -P)" && uv run python scripts/filter_for_revision.py <feature_dir>)
If output is SKIP, stop the loop and proceed to Step 5.
4b. Launch revise agent (fork):
Read the revise agent prompt from ${CLAUDE_SKILL_DIR}/prompts/revise-agent.md.
Launch with substitutions and omit the Agent model parameter so normal
runtime resolution uses this review skill's active model:
{CLAUDE_SKILL_DIR}=${CLAUDE_SKILL_DIR}(the selected review skill directory){FEATURE_DIR}= feature directory path{STRATEGY_FILE_PATH}=strategy_file_pathfrom Step 1{ADDITIONAL_DOCS_CONTENT}=additional_docs_resultfrom Step 1 (refreshed by Step 4e on later cycles)
The revise agent edits TestPlan.md (only sections mapped to failing criteria) and sets auto_revised=true.
4c. Check if reassessment is needed:
(cd "$(cd -P "${CLAUDE_SKILL_DIR}/../.." && pwd -P)" && uv run python scripts/frontmatter.py read <feature_dir>/TestPlanReview.md)
If auto_revised is false, stop; the revise agent found nothing to change.
Increment reassess_cycle. If reassess_cycle >= 2, stop — max cycles reached. Proceed to Step 5.
4d. Save cumulative state:
(cd "$(cd -P "${CLAUDE_SKILL_DIR}/../.." && pwd -P)" && uv run python scripts/preserve_review_state.py save <feature_dir>)
4e. Re-score:
Delete the existing review file to force a clean re-assessment:
rm <feature_dir>/TestPlanReview.md
Recompute results against revised TestPlan.md; the revise agent may change Sections 4, 6.2, 9.2,
or citations, so refresh all four before re-scoring:
repo_root=$(cd -P "${CLAUDE_SKILL_DIR}/../.." && pwd -P)
gate_result=$(cd "$repo_root" && uv run python scripts/build_citation_inputs.py <feature_dir> --strategy-file "$strategy_file_path") || {
echo "ERROR: scripts/build_citation_inputs.py failed — stopping review." >&2
echo "$gate_result" >&2
exit 1
}
interface_coverage_result=$(printf '%s\n' "$gate_result" | jq -c '.interface_coverage_result')
ac_citations_result=$(printf '%s\n' "$gate_result" | jq -c '.ac_citations_result')
ac_coverage_result=$(printf '%s\n' "$gate_result" | jq -c '.ac_coverage_result')
scope_coverage_result=$(printf '%s\n' "$gate_result" | jq -c '.scope_coverage_result')
actionability_result=$(printf '%s\n' "$gate_result" | jq -c '.actionability_result')
additional_docs_raw=$(cd "$repo_root" && uv run python scripts/resolve_additional_docs.py <feature_dir>) || {
echo "ERROR: scripts/resolve_additional_docs.py failed — stopping review." >&2
echo "$additional_docs_raw" >&2
exit 1
}
additional_docs_result=$(printf '%s\n' "$additional_docs_raw" | jq -c '.docs')
team_list=$(cd "$repo_root" && uv run python scripts/get_component_test_dir.py --teams-only <feature_dir>) || {
echo "ERROR: scripts/get_component_test_dir.py --teams-only failed — stopping review." >&2
echo "$team_list" >&2
exit 1
}
scope_check_result=$(cd "$repo_root" && uv run python scripts/validate_test_scope.py <feature_dir>/TestPlan.md \
--include-teams="$team_list" --checks-dir=scripts/checks) || {
echo "ERROR: scripts/validate_test_scope.py failed — stopping review." >&2
echo "$scope_check_result" >&2
exit 1
}
boilerplate_result=$(cd "$repo_root" && uv run python scripts/detect_boilerplate.py <feature_dir>/TestPlan.md \
--include-teams="$team_list" --checks-dir=scripts/checks) || {
echo "ERROR: scripts/detect_boilerplate.py failed — stopping review." >&2
echo "$boilerplate_result" >&2
exit 1
}
Repeat Step 2 with revised TestPlan.md and recomputed results; omit Agent model as above.
4f. Re-review: Omit the Agent model parameter as specified above.
Repeat Step 3 with {FIRST_PASS}=false, then Step 3.5 against the recomputed Step 4e results.
4g. Restore before_scores and revision history:
(cd "$(cd -P "${CLAUDE_SKILL_DIR}/../.." && pwd -P)" && uv run python scripts/preserve_review_state.py restore <feature_dir>)
4h. Check criteria again:
Read review frontmatter. Stop if all criteria are 2; return to 4a if any remain < 2 and cycles
remain. Otherwise proceed to Step 5.
Step 5: Present Results
Keep persistent strategy_file_path for future reviews and re-scores.
Read the final review file and present a summary to the user:
## Test Plan Review — {feature_name}
**Score: {score}/10 — Verdict: {verdict}**
| Criterion | Score |
|-----------|-------|
| Specificity | {n}/2 |
| Grounding | {n}/2 |
| Scope Fidelity | {n}/2 |
| Actionability | {n}/2 |
| Consistency | {n}/2 |
{If before_score differs from score:}
**Delta: {before_score} → {score} ({+/-difference})**
{If verdict = Ready:}
The test plan is ready for test case generation. Any `actionability_result.advisory_gaps` remain
visible follow-up items in `TestPlanGaps.md` and do not block this step. Run
`/test-plan-create-cases <feature_dir>` to proceed.
{If verdict = Revise (after max cycles):}
The test plan improved but still has issues. Review `<feature_dir>/TestPlanReview.md` for remaining feedback. Consider providing additional source documents (ADR, API spec) to resolve blocking grounding or operational gaps; advisory actionability gaps alone do not require them.
{If verdict = Rework:}
The test plan needs significant rework. This may indicate the source strategy lacks sufficient detail. Review `<feature_dir>/TestPlanReview.md` for specific issues and provide source documents for blocking grounding or operational gaps; advisory actionability gaps alone do not require them.
{If this plan is already in an open PR and reviewer comments exist:}
Use `/test-plan-resolve-feedback <PR_URL>` to triage and apply PR feedback items.
Anti-hallucination Rules
The score agent MUST follow these constraints when reviewing and suggesting improvements:
NEVER:
- Invent resolution paths for TBDs (e.g., "check version in ADR section 3" when no ADR exists or that section doesn't specify versions)
- Add specific requirements, API endpoints, or version constraints not present in source documents
- Fabricate documentation references ("see design doc for details" when no design doc exists)
- Assume information exists in documents without verifying
- Create specificity improvements by inventing details
ALWAYS:
- For
actionability == 2, retain a genuinely unknown required value only asTBD — Resolution: {concrete action} from/with/by/before/after/using {named source or timing}. The resolution path must be grounded in an actual source or a known owner/timing;deriveis valid when the named source grounds the derivation. Apply this rule independently to each TBD in Sections 3.1, 3.2, and 3.3. Missing or vague OpenShift/RHOAI versions and incomplete test-data format/examples may remain as advisory gaps and do not by themselves prevent Actionability 2/2. - Ground all improvements in actual source document content (strategy, ADR, additional_docs)
- Keep bare/unresolved TBDs, missing or non-substantive Section 3.1 environment/configuration, and unusable or broad-collection RBAC evidence as blocking actionability gaps; they must not support an Actionability score of 2/2. Keep advisory gaps visible in TestPlanGaps.md without requesting source documents solely for those advisories.
- Defer to TestPlanGaps.md for unresolved items
- Only suggest changes that are directly traceable to source material
These rules keep the review grounded in source documents: assess completeness and consistency, and acknowledge gaps instead of inventing resolution paths or details.
What This Skill Does NOT Do
- Does NOT generate test plans (use
/test-plan-create) - Does NOT generate test cases (use
/test-plan-create-cases) - Does NOT modify the source strategy
- Does NOT submit anything to Jira
- Does NOT resolve GitHub PR comments (use
/test-plan-resolve-feedback <PR_URL>)
$ARGUMENTS