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: /smoke-check
Skill Summary
/smoke-check is the gate between implementation and QA hand-off. Phase 1 runs
project-coherence.sh, verifies that game test files exist under the
engine's test root or tests/smoke/ (not merely that tests/ exists), and
reads the engine from project.yaml (falling back to technical-preferences.md),
then resolves the editor executable — the one commands.test names, else
engine.path, else the name on PATH — before it calls the runner unavailable.
Phase 2 runs commands.test (or the per-engine default) under a timeout and
reads the result by engine — gdUnit4 exit codes for Godot; on Unity, Play Mode
tests under Assets/Tests/PlayMode/ get a second run with their own results
file. At qa.level: minimal with no game tests, the automated row is WAIVED
and Phase 2 runs a build check instead — commands.smoke, else
commands.build, when set. Phase 3 scans test coverage against the QA plan or
sprint stories; Phase 4 batch-verifies manual smoke checks with
AskUserQuestion, and Batch 1 first asks whether the build was launched this
session — "not launched" makes the manual checks NOT ASSESSED. The report is written
to production/qa/smoke-[date].md after explicit approval.
Verdicts, first matching rule wins: FAIL (a test failure, a suite that did not
complete — timeout, compile/parse/build error, no results file — or a Batch
1/Batch 2 check FAILED, or a failed build check), NOT ASSESSED (suite unconfirmed NOT RUN, a runner that
could not run or executed zero tests, unconfirmed PlayMode: NOT RUN, a check
that could not execute — including a build not launched this session — an
UNKNOWN coverage row, Batch 3 skipped — or no game tests at qa.level
standard/full; at minimal the automated row is WAIVED and the batches carry
the verdict), PASS WITH WARNINGS (MISSING test evidence, a runner warning such
as gdUnit4 101's leaked orphans, or a NOT RUN suite the developer confirmed),
PASS. NOT ASSESSED ranks above both pass values and below FAIL.
Whether a FAIL blocks hand-off depends on qa.level (advisory at minimal)
and testing.strict.config (unset → blocking).
No director gates apply. The skill does NOT invoke any director agents.
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: PASS, PASS WITH WARNINGS, NOT ASSESSED, FAIL
- Contains "May I write" collaborative protocol language before writing the report
- Has a next-step handoff (re-run
/smoke-checkafter a FAIL; QA hand-off to qa-tester on PASS)
Director Gate Checks
None. /smoke-check is a pre-QA utility skill. No director gates apply.
Test Cases
Case 1: Happy Path — Automated tests pass, manual items confirmed, PASS
Fixture:
project.yamlhasengine.name: Godotandcommands.testset to the gdUnit4 commandaddons/gdUnit4/bin/GdUnitCmdTool.gdexists; game tests exist undertests/unit/andtests/integration/production/qa/qa-plan-sprint-005.mdexists- The runner exits 0 with 12 tests, 12 passing
- The developer says the build was launched this session and selects no FAILED item in Batch 1, Batch 2 or Batch 3
- Every sprint story is COVERED or EXPECTED (no MISSING, no UNKNOWN)
Input: /smoke-check
Expected behavior:
- Phase 1 reports environment: Godot, tests found, QA plan path
- Runs
godot --headless --path . --import, thencommands.test(godot --headless -s -d --remote-debug tcp://127.0.0.1:0 res://addons/gdUnit4/bin/GdUnitCmdTool.gd -a res://tests --ignoreHeadlessMode) undertimeout 900; the report sayscommands.testwas the command run - Exit 0 with tests found → automated tests PASS (12/12)
- Coverage scan: all stories COVERED or EXPECTED
AskUserQuestionfor Batch 1, Batch 2 and Batch 3 — nothing selected- Report names its checklist source (the QA plan's Smoke Test Scope)
- Asks "May I write this smoke check report to
production/qa/smoke-[date].md?" - Writes after approval; delivers "Smoke check passed cleanly. The build is ready for manual QA."
Assertions:
- Automated test runner is invoked via Bash, after the Godot import
AskUserQuestionis used for manual smoke check batches- "May I write" is asked before writing the report file
- Report is written to
production/qa/smoke-[date].md - Verdict is PASS
Case 2: Failure Path — Automated test fails, blocking FAIL
Fixture:
- As Case 1, but
project.yamlsetsmodes.rigor: standard(soqa.levelresolves tostandard) and leavestesting.strictunset - The runner exits 100: 10 tests run, 8 passing, 2 failing —
test_health_clamp_at_zero,test_damage_calculation_negative
Input: /smoke-check
Expected behavior:
- Skill runs automated tests via Bash; exit 100 → FAIL
- Records both failing test names
- Proceeds through the manual smoke check batches
- Report shows automated tests as FAIL with the failing test names listed
- Asks to write the report; writes after approval
- Gate is blocking (
qa.level: standard,testing.strict.configunset → blocking) - Delivers: "The smoke check failed. Do not hand off to QA until these failures
are resolved: …" listing each failure, then "Fix the failures and run
/smoke-checkagain to re-gate before QA hand-off."
Assertions:
- Failing test names are listed in the report
- Verdict is FAIL
- The blocking message directs the developer to fix failures before QA hand-off
/smoke-checkre-run is suggested after fixing
Case 3: Manual Confirmation — MISSING coverage, PASS WITH WARNINGS
Fixture:
- As Case 1: runner exits 0 with 8/8 passing
- One Logic story has no matching test file (MISSING coverage); no UNKNOWN rows
- The developer selects no FAILED item in Batch 1, Batch 2 or Batch 3
Input: /smoke-check
Expected behavior:
- Automated tests PASS
- Coverage scan finds 1 MISSING entry for a Logic story
AskUserQuestionis used for Batches 1–3 — nothing selected- Report shows automated tests PASS, manual checks PASS, and the MISSING entry under "Missing Test Evidence" with its expected test path
- Verdict is PASS WITH WARNINGS; the message says the build is ready for manual
QA and lists the MISSING entry as an advisory item to resolve before
/story-doneon the affected story - Asks to write the report; writes after approval
Assertions:
AskUserQuestionis used for manual smoke check batches (not inline text prompts)- MISSING test coverage entry appears in the report
- Verdict is PASS WITH WARNINGS (not PASS, not FAIL)
- Advisory note says the MISSING entry must be resolved before
/story-done - Report file is written to
production/qa/smoke-[date].md
Case 4: Scaffold Without Game Tests — NOT ASSESSED, then stop
Fixture:
project.yamlsetsmodes.rigor: standard(soqa.levelresolves tostandard)- Engine is Godot;
tests/unit/andtests/integration/exist but hold only the.gdignore_placeholderfiles/test-setupcreates - No
tests/smoke/directory
Input: /smoke-check
Expected behavior:
- Phase 1 counts game test files, not directories — none are found
- Skill delivers: "Smoke check: NOT ASSESSED — no game tests found under
[the test root] or
tests/smoke/. Run/test-setupto scaffold the testing infrastructure, or point me at where tests live." - Skill stops — no automated run, no manual batches, no report written
Assertions:
- The presence of
tests/and its subdirectories is not treated as tests existing - Verdict is NOT ASSESSED — the skill does not stop without a verdict
/test-setupis suggested as the remediation step- No further phases run and no report file is written
Case 4b: No Game Tests at qa.level: minimal — WAIVED, the build carries the verdict
Fixture:
- No
modes.rigorset (the default,minimal, soqa.levelresolves tominimal) - Engine is Godot; no game test files under
tests/unit/,tests/integration/ortests/smoke/ commands.smokeisgodot --headless --quit-after 5; it exits 0 with noSCRIPT ERRORline- The user says the build was launched this session and confirms every Batch 1, Batch 2 and Batch 3 check
Input: /smoke-check
Expected behavior:
- Phase 1 finds no game test files and, at
qa.level: minimal, records the automated row asWAIVED — qa.level: minimal, no game tests - Phase 2's test run and Phase 3 are skipped, each with a one-line note saying why
- Phase 2's build check runs
commands.smoke(after the Godot import) under a timeout:Build check: PASS - Phase 4 runs Batches 1–3; every check passes
- Verdict: PASS — the report's Automated Tests status reads WAIVED
Assertions:
- The missing tests are WAIVED, not NOT ASSESSED, because
qa.levelisminimal - The skipped test run and Phase 3 are announced, not silent
- The build check runs
commands.smokeand its result is in the report - Batch 1 and Batch 2 still run — PASS is never given without them
- Verdict is PASS and the report's Automated Tests row reads WAIVED
- A Batch 1 or Batch 2 check that could not be executed (no build) makes the verdict NOT ASSESSED, as at any level
- Variant — the user answers that the build was not launched this session: Batches 2 and 3 are not asked (the skip is said), the Batch 1 checks read
NOT RUN — build not launched this session, and the verdict is NOT ASSESSED — never PASS from an empty failure list - Variant —
commands.smokeprintsSCRIPT ERROR(Godot's boot check exits 0 on it): the build check is FAIL and so is the verdict
Case 5: Director Gate Check — No gate; smoke-check is a QA pre-check utility
Fixture:
- Valid test setup, automated tests pass, manual smoke checks confirmed
Input: /smoke-check
Expected behavior:
- Skill runs all phases and produces a verdict
- No director agents are spawned at any point
- No gate IDs (CD-, TD-, AD-, PR-) appear in output
- No
/gate-checkis invoked
Assertions:
- No director gate is invoked
- No gate skip messages appear
- Verdict is PASS, PASS WITH WARNINGS, NOT ASSESSED, or FAIL — no gate verdict involved
Case 6: gdUnit4 Lookalikes — exit 0 over zero tests, and exit 101
Fixture:
- As Case 1, with every story COVERED or EXPECTED and nothing selected in Batch 1, Batch 2 or Batch 3
- Run A: the suites under
tests/unit/declare no test functions; the runner printsNo test cases foundand exits 0 - Run B: the runner exits 101 — 12 tests, 12 passing, 2 orphan nodes leaked
Input: /smoke-check
Expected behavior:
- Run A: exit 0 is not read as a pass — the automated tests are
NOT ASSESSED — no tests found, and the verdict is NOT ASSESSED even though every manual check passed - Run B: 101 is PASS WITH WARNINGS, naming the orphan count; with no MISSING entry, the verdict is PASS WITH WARNINGS and the advisory list names the orphans
Assertions:
- Run A's verdict is NOT ASSESSED — not PASS
- Run B's Status line is PASS WITH WARNINGS with the orphan count
- Run B's verdict is PASS WITH WARNINGS — not PASS, and not FAIL
Case 7: Timeout — the suite never completed, FAIL
Fixture:
- As Case 2 (
qa.level: standard,testing.strictunset) - The runner hangs;
timeout 900ends it with exit 124 and no summary printed
Input: /smoke-check
Expected behavior:
- Exit 124 is a gate FAILURE, never a pass — the run did not complete and nothing was verified
- The Status line reads FAIL, naming the timeout
- The verdict is FAIL and the gate is blocking
Assertions:
- A timeout is FAIL — not NOT ASSESSED and not NOT RUN
- The report names the timeout as the reason
- The blocking message is delivered
Case 8: Batch 3 Skipped — NOT ASSESSED, not PASS
Fixture:
- As Case 1: runner exits 0 with 12/12 passing, no MISSING or UNKNOWN rows
- In Batch 3 the developer selects only "Performance not checked this session"
Input: /smoke-check
Expected behavior:
- Batches 1 and 2 pass
- The skipped Batch 3 item is recorded as not checked — it is not a FAIL
- The verdict is NOT ASSESSED, and the Phase 6 message names the performance check that did not run
Assertions:
- Verdict is NOT ASSESSED — not PASS, not FAIL
- The unchecked performance item is named in the report
Case 9: Unity — Play Mode is a second run
Fixture:
engine.name: Unity;com.unity.test-frameworkis inPackages/manifest.jsoncommands.testis the Edit Mode command/setup-enginewritesAssets/Tests/EditMode/andAssets/Tests/PlayMode/both hold test files- Edit Mode exits 0 with 8/8 passing; Play Mode exits 2 with 1 of 3 failing
Input: /smoke-check
Expected behavior:
- The Edit Mode run deletes
test-results/editmode.xmlfirst, then runs - Because
commands.testdoes not pass-testPlatform PlayModeandAssets/Tests/PlayMode/holds tests, the skill runs Play Mode too, deleting and then writingtest-results/playmode.xml - The report has one Status line per platform:
EditMode: PASS (8/8)andPlayMode: FAIL (1 failure) - The verdict is FAIL
Assertions:
- Play Mode is run as a second run with its own results file
- Each platform is reported on its own line
- The Play Mode failure makes the verdict FAIL — the Edit Mode pass does not cover it
Protocol Compliance
- Uses
AskUserQuestionfor all manual smoke check batches (Batch 1, Batch 2, Batch 3), and Batch 1 asks whether the build was launched this session; "not launched" yields NOT ASSESSED - Resolves the editor executable from
commands.test, thenengine.path, thenPATHbefore reporting the runner unavailable; a baregodotthat does not resolve is never read as "no engine installed" - Never edits
project.yaml: acommands.testmissing flags or using a relative path is adjusted for the run and reported to the user - Runs automated tests via Bash before asking any manual questions
- Asks "May I write" before creating the report file — never writes without approval
- Verdict vocabulary is PASS / PASS WITH WARNINGS / NOT ASSESSED / FAIL — no other verdicts
- FAIL is triggered by automated test failures, a suite that did not complete (timeout, compile/parse/build error, no results file), or Batch 1/Batch 2 FAIL responses
- PASS WITH WARNINGS is triggered by MISSING test coverage, a runner warning on a passing suite (gdUnit4 101), or a confirmed NOT RUN — with no critical failures
- Unconfirmed NOT RUN (runner not available) yields NOT ASSESSED — never FAIL, never a pass verdict
- A run over zero tests, a runner that could not run, and an unconfirmed
PlayMode: NOT RUNyield NOT ASSESSED - Does not invoke director gates at any point
Coverage Notes
- The
quickargument (skips the Phase 3 coverage scan and Batch 3) is not separately fixture-tested. - The
--platformargument adds platform-specificAskUserQuestionbatches and a per-platform verdict table; not separately tested here. - The runner-not-available path (NOT RUN, then NOT ASSESSED until the developer confirms a result from their IDE or CI) is covered by the protocol compliance assertions above.
- At
qa.level: minimala FAIL is advisory and never blocks hand-off; Case 2 pinsstandardto test the blocking path. Unity'stestcasecount="0"and Unreal'sNo automation tests matched(→ NOT ASSESSED) follow Case 6 Run A; not separately fixture-tested. - The Linux and macOS Unreal commands (picked by
uname -s) are not fixture-tested. On macOS, Unreal tests are NOT ASSESSED untilcommands.testis set — asserted in the skill, not given a case here. - A Play Mode run that cannot be made (
PlayMode: NOT RUN, unconfirmed → NOT ASSESSED) is covered by protocol compliance, not by its own case.