Fix release for 1.1.1: the minimal path end to end, engine commands that run on Godot, Unity and Unreal, hook warnings you can see, and verdicts that say when they could not check. Details in CHANGELOG.md; upgrade steps in UPGRADING.md (v1.1.1 → v1.1.2). Fixes #67 Fixes #131 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WuJn2FdynEybfL5KC697nJ
16 KiB
Skill Test Spec: /test-evidence-review
Skill Summary
/test-evidence-review reviews the quality of the test files and evidence
documents behind stories — /smoke-check covers existence and passing. It
takes a scope: a single story path, sprint (the most recent plan in
production/sprints/), or a system name (production/epics/[system]/story-*.md);
with no argument it asks which. If the resolved scope holds zero stories it stops
with NOT ASSESSED — no stories in scope and routes to the skill that creates
them. For each story it collects Story Type, evidence path and acceptance
criteria with targeted greps, locates the evidence (tests/unit/[system]/,
tests/integration/[system]/, production/qa/evidence/,
production/qa/smoke-*.md), and reviews automated tests for assertion count,
edge cases, naming and formula traceability, and manual evidence for criterion
linkage, sign-offs, retained screenshots and freshness. It resolves qa.level
and testing.strict the way /story-done does: at qa.level: minimal a Logic,
Integration or Config/Data story with no test evidence is
WAIVED (qa.level: minimal), not MISSING, and a gap in a test that does exist is
ADVISORY; Visual/Feel and UI evidence is never waived; otherwise each gap takes
its story type's gate level from testing.strict.<type> (unset → a
plain-boolean testing.strict legacy value applies to every type; else the
coding-standards.md default). Each story gets ADEQUATE, INCOMPLETE, MISSING,
NOT ASSESSED or WAIVED; the overall verdict is the worst present, with NOT
ASSESSED outranking ADEQUATE only, and WAIVED below ADEQUATE. The report is presented in
conversation, and the skill asks "May I write this test evidence review to
production/qa/evidence-review-[date].md?" as an optional step. It never
modifies test files or evidence docs and invokes no director gates. The run ends
COMPLETE, or CONCERNS when BLOCKING items were found.
Static Assertions (Structural)
Checked against the SKILL.md by /skill-test spec — no fixture needed.
- Has required frontmatter fields:
name,description,argument-hint,user-invocable,allowed-tools - Has ≥2 phase headings
- Contains verdict keywords: ADEQUATE, INCOMPLETE, MISSING, NOT ASSESSED, and the run-level COMPLETE / CONCERNS
- Contains "May I write" language for the optional
production/qa/evidence-review-[date].md - Has a next-step handoff (Section 7: BLOCKING items before
/story-done,/test-helpers, sign-off routing) - The
resolve_configinjection namesqa.levelandtesting.strict, and the skill states theWAIVED (qa.level: minimal)result and the per-typetesting.strict.<type>gate level
Director Gate Checks
None. Test evidence review is an advisory quality skill; no gates are invoked.
Test Cases
Case 1: Happy Path — Single Logic story with a strong test
Fixture:
project.yaml:engine.name: godot, so the unit-test root istests/unit/;modes.automationunset (collaborative)production/epics/combat/story-001-damage-calc.md(slugdamage-calc):## Test Evidencegives Story Type Logic and the pathtests/unit/combat/damage-calc_test.gd;## Acceptance Criteriaincludes "damage is never below 1" and "critical hits multiply damage by 2.0"tests/unit/combat/damage-calc_test.gdopens with# Story: production/epics/combat/story-001-damage-calc.md, has 4 test functions with 3+ assertions each, named liketest_damage_calc_zero_armor_returns_base_damageandtest_damage_calc_max_crit_doubles_damage, and names the GDD'sdamage_formulain a comment
Input: /test-evidence-review production/epics/combat/story-001-damage-calc.md
Expected behavior:
- Single-story mode: skill reads the story's Test Evidence and Acceptance Criteria
- Section 3 finds the test file under
tests/unit/combat/ - Section 4: assertion coverage is normal (3+ per function); "zero" and "max" cases cover the numeric criteria; names follow
test_[scenario]_[expected]; the formula is referenced - Story verdict: ADEQUATE; overall verdict ADEQUATE; 0 BLOCKING items
- Report is presented in conversation, then the skill asks "May I write this test evidence review to
production/qa/evidence-review-[date].md?" as optional - Verdict: COMPLETE
Assertions:
- The test file is located under
tests/unit/combat/for the Logic story - Assertion coverage, edge cases, naming and formula traceability are each reported
- Story verdict is ADEQUATE
- The report is shown before the optional "May I write" prompt
- Run ends with COMPLETE
Case 2: Vacuous and Thin Tests — INCOMPLETE with a BLOCKING item
Fixture:
project.yaml:engine.name: godot,modes.rigor: standard(soqa.level: standard— tests are required); notesting.strictblockproduction/epics/combat/story-002-crit-roll.md(slugcrit-roll): Story Type Logictests/unit/combat/crit-roll_test.gdhasfunc test_1()with no assertions andfunc test_crit_roll_applies_multiplier()with one assertion
Input: /test-evidence-review production/epics/combat/story-002-crit-roll.md
Expected behavior:
- Skill finds the test file and counts assertions per function
test_1has 0 assertions → flagged BLOCKING (passes vacuously, proves nothing);test_1is also flagged as a generic name- The one-assertion function is noted as potentially thin
- Story verdict: INCOMPLETE
- Follow-ups: "These must be resolved before
/story-donecan mark the story Complete…" and "Consider running/test-helpers [system]…" - Run ends with CONCERNS because BLOCKING items were found
Assertions:
- A test function with zero assertions is flagged BLOCKING
test_1is flagged as a naming issue- Story verdict is INCOMPLETE, not ADEQUATE
- Output states BLOCKING items must be resolved before
/story-doneand suggests/test-helpers - Run ends with CONCERNS
- The test file is not modified
- Variant — with
testing.strict.logic: falseinproject.yaml, the zero-assertion finding is ADVISORY, the story is still INCOMPLETE, and the run ends COMPLETE
Case 3: Empty Sprint Scope at Minimal Workflow — NOT ASSESSED, not a clean report
Fixture:
project.yaml:modes.rigor: minimal(the resolved block showsworkflow: minimal)production/sprints/does not exist
Input: /test-evidence-review sprint
Expected behavior:
- Sprint mode finds no sprint plan, so the scope holds zero stories
- Skill stops and reports
NOT ASSESSED — no stories in scope, naming the sprint scope and the emptyproduction/sprints/path - Because the workflow is minimal (no sprints), it routes to
/test-evidence-review [epic-slug]instead of/sprint-plan new - Skill does not continue to Section 3; no Summary table and no "BLOCKING items: 0" line are rendered
Assertions:
- Verdict is NOT ASSESSED — no stories in scope
- Output names which scope was searched and which path was empty
- At minimal workflow the route is
/test-evidence-review [epic-slug], not/sprint-plan new - No empty report reading "BLOCKING items: 0 / ADVISORY items: 0" is produced
Case 4: Story Type Unknown — Per-story NOT ASSESSED lifts the overall verdict
Fixture:
project.yaml:engine.name: godotproduction/epics/combat/containsstory-001-damage-calc.md(Logic, test as in Case 1) andstory-003-hit-feedback.md, whose## Test Evidencesection states no Story Type and no evidence path- Full-reading
story-003-hit-feedback.mddoes not reveal a type either
Input: /test-evidence-review combat
Expected behavior:
- System mode globs
production/epics/combat/story-*.mdand finds both stories story-001-damage-calcis ADEQUATEstory-003-hit-feedbackis NOT ASSESSED, with the reason stated: the story type cannot be determined, so the required evidence is unknown- It is not reported as MISSING
- Overall verdict: NOT ASSESSED — it outranks ADEQUATE
Assertions:
- The untyped story is NOT ASSESSED, not MISSING
- The NOT ASSESSED reason (type cannot be determined) is stated for that story
- Overall verdict is NOT ASSESSED, not ADEQUATE
- The typed story is still reviewed and reported ADEQUATE
Case 5: Visual/Feel Evidence Without a Retained Screenshot, Full Review Mode — No gates
Fixture:
project.yaml:engine.name: godot,modes.review_mode: fullproduction/epics/combat/story-004-hit-flash.md(slughit-flash): Story Type Visual/Feelproduction/qa/evidence/hit-flash-evidence.mdreferences every acceptance criterion, has every sign-off filled, and is dated after the sprint start- No
*.png,*.jpgor*.giffor this story exists inproduction/qa/evidence/
Input: /test-evidence-review production/epics/combat/story-004-hit-flash.md
Expected behavior:
- Section 3 finds
production/qa/evidence/hit-flash-evidence.md - Section 5: criterion linkage and sign-offs are complete, but no retained image exists for the story
- The doc describes a visual check without evidence → story verdict INCOMPLETE, flagged BLOCKING
- No director gate is invoked regardless of review mode
- The evidence document is not edited
- Run ends with CONCERNS
Assertions:
- A Visual/Feel evidence doc with no retained image is INCOMPLETE, even with complete sign-offs
- The missing screenshot is listed as a BLOCKING issue
- No director gate is invoked in any review mode
- The evidence document is not modified
Case 6: UI Story Closed by Its Screenshots — No Sign-Off Required
Fixture:
project.yaml:engine.name: godotproduction/epics/ui/story-007-inventory-screen.md(sluginventory-screen): Story Type UI; its## Test Evidencenamesproduction/qa/evidence/inventory-screen-populated.pngandproduction/qa/evidence/inventory-screen-empty.png— one per screen state it touches- Both images exist and are dated after the sprint start
- No
inventory-screen-evidence.md, no sign-off record and no walkthrough log exist
Input: /test-evidence-review production/epics/ui/story-007-inventory-screen.md
Expected behavior:
- Section 3 uses the two paths the story states before searching
- Section 5: a UI story is closed by the retained screenshot of each screen it touched
- Story verdict ADEQUATE; run ends with COMPLETE
Assertions:
- The story's stated evidence paths are checked first, and both images are found
- The missing sign-off does not make the story INCOMPLETE — a UI story needs none
- No walkthrough log is demanded
- Story verdict is ADEQUATE
Case 7: Unity Logic Story — Evidence at the Stated Engine Path
Fixture:
project.yaml:engine.name: unityproduction/epics/inventory/story-003-stack-merge.md: Story Type Logic; its## Test EvidencenamesAssets/Tests/EditMode/InventoryTests.cs- That file exists and holds one
[Test]per acceptance criterion (4 in all), each with 3 or moreAssert.Thatcalls, named in the[Scenario]_[Expected]form (MergeStacks_FullStack_SpillsToNewSlot), with the empty-stack and max-stack cases covered - The inventory GDD has no Formulas section, so formula traceability does not apply
- Nothing named
stack-merge_test.*exists anywhere, andtests/unit/does not exist
Input: /test-evidence-review production/epics/inventory/story-003-stack-merge.md
Expected behavior:
- Section 3 finds the test at the stated path under
Assets/Tests/EditMode/ - Section 4 reviews its assertions; story verdict ADEQUATE
Assertions:
- The test is found at the stated Unity path — the story is not reported MISSING
- The review does not require a
[story-slug]_test.*name or atests/unit/location
Case 8: No Test for a Logic Story — MISSING, and it outranks NOT ASSESSED
Fixture:
project.yaml:engine.name: godot,modes.rigor: standard(soqa.level: standard— a Logic story's test is required)production/epics/combat/containsstory-003-hit-feedback.md(no Story Type, as in Case 4) andstory-005-knockback.md: Story Type Logic, no evidence path stated- No test exists for it:
tests/unit/combat/does not exist, and no other test file names the slugknockbackor references the story
Input: /test-evidence-review combat
Expected behavior:
story-005-knockbackis a Logic story, so a unit test is required; the search of the unit-test root finds nonestory-005-knockbackis MISSING — the review ran and found nothing — and the gap is a BLOCKING itemstory-003-hit-feedbackis NOT ASSESSED (its type cannot be determined), not MISSING- Overall verdict: MISSING — a known failure outranks the story that could not be assessed
- Run ends with CONCERNS because a BLOCKING item was found
Assertions:
- The Logic story with no test file is MISSING, not NOT ASSESSED
- The untyped story is NOT ASSESSED, not MISSING — the two are kept apart
- Overall verdict is MISSING, not NOT ASSESSED
- The missing test is listed as BLOCKING and the run ends with CONCERNS
Case 9: qa.level: minimal — a Logic Story With No Test Is WAIVED; a UI Screenshot Is Not
Fixture:
project.yaml:engine.name: godot, nomodesblock (soqa.level: minimal— tests are waived)production/epics/combat/containsstory-005-knockback.md(Logic, no test anywhere, as in Case 8) andstory-008-pause-menu.md(UI, no retained image for it inproduction/qa/evidence/)
Input: /test-evidence-review combat
Expected behavior:
story-005-knockbackis WAIVED (qa.level: minimal) — not MISSING — and raises no BLOCKING itemstory-008-pause-menuis MISSING and BLOCKING — the retained screenshot a UI story needs is never waived- Overall verdict: MISSING; the report's header shows
qa.level: minimal - Run ends with CONCERNS because of the UI story alone
- Variant — without the UI story, the overall verdict is WAIVED, there are 0 BLOCKING items, and the run ends COMPLETE
Assertions:
- The Logic story with no test is WAIVED (qa.level: minimal), not MISSING, and is not listed as BLOCKING
- The UI story with no screenshot is still MISSING and BLOCKING at
qa.level: minimal - The waived story is named as waived in the report, not dropped from it
- Variant: an all-waived scope reads WAIVED, with no "must be resolved before
/story-done" prompt
Protocol Compliance
- Collects Story Type, evidence path and acceptance criteria with targeted
## Test Evidence/## Acceptance Criteriagreps, full-reading a story only when its Test Evidence section is missing or ambiguous - An empty scope stops with
NOT ASSESSED — no stories in scope, naming the scope and routing onward - Each story gets ADEQUATE, INCOMPLETE, MISSING or NOT ASSESSED, and a NOT ASSESSED story states its reason
- Overall verdict is the worst story verdict; NOT ASSESSED outranks ADEQUATE but not INCOMPLETE or MISSING; WAIVED ranks below ADEQUATE
- At
qa.level: minimala Logic, Integration or Config/Data story with no test evidence is WAIVED, never MISSING or BLOCKING; Visual/Feel and UI evidence is never waived - BLOCKING vs ADVISORY per story type comes from
testing.strict.<type>in the resolved block, falling back to a legacy plain-booleantesting.strictvalue applied to every type, then to thecoding-standards.mddefault - The report is presented before the optional "May I write" for
production/qa/evidence-review-[date].md - Does not modify test files or evidence documents
- No director gates are invoked
- Ends with COMPLETE, or CONCERNS when BLOCKING items were found — except an empty scope, which stops at
NOT ASSESSED — no stories in scopebefore any report (Case 3)
Coverage Notes
- Integration stories (playtest records in
production/session-logs/) and Config/Data stories (production/qa/smoke-*.md) are not tested here. - The no-argument path (the skill asks which scope to review) is not tested.
- The POTENTIALLY STALE freshness flag is not tested separately.