Release 1.1.0 — one config file, and a process weight you choose

`project.yaml` at the repo root is now the single source of truth for a
project's configuration, replacing `production/stage.txt`,
`production/review-mode.txt` and `.claude/docs/technical-preferences.md` (all
three still read as a fallback; nothing is force-deleted).

**`modes.rigor` — one question instead of six.** `minimal`, `standard` or
`full` fronts six knobs: `modes.workflow`, `docs.density`, `qa.level`,
`modes.story_granularity`, `modes.review_mode` and `team.size`. It defaults to
`minimal`, which is a change from 1.0's behaviour: the heavier tier was
measured costing several times more to reach working code without producing a
better result, and returning nothing when a fresh developer inherited the
project. Pin `modes.rigor: standard` to keep 1.0's process level.

Also in this release:

- `/settings` to view or change any setting, and `project.local.yaml` for
  per-developer preferences that should not be committed.
- `system_overrides` to hold one system to a higher standard than the rest of
  the project, and `testing.strict` to set per test type whether missing
  evidence blocks a story or warns.
- A story that changes anything the player sees is not closed until the game
  has been launched, observed, and a screenshot retained in
  `production/qa/evidence/`. A parse check is not a run. This holds at every
  rigor level, including `minimal`, where automated tests are waived but the
  look is not.
- The code root is engine-specific and enforced as such: Godot `src/`, Unity
  `Assets/`, Unreal `Source/<Module>/`. Two of the three will not build a
  project laid out any other way.
- Migration tooling for existing projects: `.claude/scripts/migrate-v1-config.sh`
  converts a 1.0 layout, non-destructively by default, with a report to review
  before `--finalize` deletes anything.
- Ask-before-write holds regardless of personal automation settings.

Upgrading from 1.0 or earlier: see UPGRADING.md. Full list of changes:
CHANGELOG.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xvzG3i7pWQg4LjyDwWMXd
This commit is contained in:
Donchitos
2026-09-23 12:25:11 +10:00
co-authored by Claude Opus 5
parent 984023ddac
commit d05699707f
291 changed files with 24374 additions and 7740 deletions
+86
View File
@@ -0,0 +1,86 @@
# /story-done — Handoff Contract
## Role in Pipeline
Closes the implementation loop for a single story by verifying every acceptance criterion, checking test evidence, detecting GDD/ADR deviations, and — after user approval — updating the story file to `Status: Complete` with a `## Completion Notes` section.
## Inputs Required
### Files That Must Exist
| File | Required Fields / Sections | Read-Only? |
|------|---------------------------|-----------|
| `production/epics/**/*.md` (the story file) | `Status:` (expected: `In Progress`, or `Ready` if `/dev-story` was skipped — `/dev-story` sets `In Progress`, and `/story-done` is the only skill that writes `Status: Complete`), `Type:`, `Manifest Version`, TR-ID(s), ADR reference(s), `## Acceptance Criteria`, list of files to create/modify, `## Test Evidence` path | No — status and completion notes written after approval |
| `docs/architecture/tr-registry.yaml` | Entry per TR-ID with current `requirement` text | Yes |
| GDD section referenced by each TR-ID | Acceptance criteria and key rules for cross-check | Yes |
| `docs/architecture/adr-NNNN-[slug].md` (referenced ADR) | `## Decision`, `## Consequences` | Yes |
| `docs/architecture/control-manifest.md` | `Manifest Version:` header date, forbidden patterns | Yes (existence checked; absent = skip staleness check) |
| `production/session-state/active.md` | Active story path (used when no argument given) | No — session extract appended |
### Test Evidence Files (presence checked, not pre-required to exist — absence triggers gate)
| Story Type | Expected Location |
|------------|------------------|
| Logic | `tests/unit/[system]/[story-slug]_test.[ext]` |
| Integration | `tests/integration/[system]/[story-slug]_test.[ext]` OR `production/session-logs/` playtest record |
| Visual/Feel | `production/qa/evidence/[story-slug]-evidence.md` + a retained screenshot |
| UI | `production/qa/evidence/[story-slug]-evidence.md` + a retained screenshot of each screen touched |
| Config/Data | `production/qa/smoke-*.md` |
### Preconditions
- The story file must be findable either by argument path, in `production/session-state/active.md`, or in the current sprint file as `IN PROGRESS`
- Implementation must have been attempted (source files listed in the story's "files to create/modify" should exist) — this skill does not implement; it verifies
- For Logic and Integration stories, the test file at the `## Test Evidence` path must **exist** before a COMPLETE verdict is possible — unless `testing.strict` in `project.yaml` sets that type to advisory (`testing.strict.logic`/`.integration: false`), in which case a missing test is recorded as a warning but does not block
- For Visual/Feel and UI stories, a retained screenshot **and** an evidence doc must exist under `production/qa/evidence/` before a COMPLETE verdict is possible — unless `testing.strict.visual`/`.ui` is set to `false`, in which case the gap is recorded as a warning but does not block
- **This skill does not execute the test.** Phase 3 checks existence with `Glob`; nothing runs. A test that exists and FAILS satisfies the gate, so a failing test cannot block a COMPLETE verdict here — this gate is existence, not "exist and pass". Pass/fail is established by `/gate-check` and `/smoke-check`, both of which run a suite and both of which come after story closure. See the note under Phase 3 of `SKILL.md`.
## Verdicts Emitted
`COMPLETE` / `COMPLETE WITH NOTES` / `NOT ASSESSED` / `BLOCKED`.
`NOT ASSESSED` outranks `COMPLETE` — one or more acceptance criteria could not
be evaluated at all, which is not the same as evaluating them and finding them
met. Precedence is `BLOCKED`, then `NOT ASSESSED`, then `COMPLETE WITH NOTES`,
then `COMPLETE`; see the verdict section of `SKILL.md`. This contract listed no
verdict set at all, so a reader had nothing to branch on.
## Outputs Produced
### Files Written
| File | Guaranteed Fields / Sections | Notes |
|------|------------------------------|-------|
| `production/epics/**/*.md` (the story file) | `Status: Complete`, `## Completion Notes` block (date, criteria count, deviations, test evidence path, code review status) | updated after explicit user approval |
| `production/sprint-status.yaml` | `status: done`, `completed: [date]`, top-level `updated` field | updated silently alongside story file if file exists |
| `docs/tech-debt-register.md` | Advisory deviation entries (if user confirms) | appended if advisory deviations exist and user agrees |
| `production/session-state/active.md` | `## Session Extract — /story-done [date]` block: verdict, story path, tech debt count, next recommended story | appended (created if absent) |
### Output Guarantees
- `Status: Complete` is written only when verdict is COMPLETE or COMPLETE WITH NOTES and the user explicitly approves
- `## Completion Notes` always contains: completion date, criteria pass count, deviation summary, test evidence path (or "None required"), code review status
- Session state always records the final verdict and the next recommended story path
## Immutability Rules
- READS but does NOT modify: `docs/architecture/tr-registry.yaml`, `docs/architecture/adr-NNNN-[slug].md` (Decision + Consequences only), GDD sections, `docs/architecture/control-manifest.md`, source files in `src/` (Grep only), test files in `tests/` (run via Bash; not edited)
- MODIFIES: the story `.md` file (status + completion notes), `production/sprint-status.yaml` (if exists), `production/session-state/active.md` (append), `docs/tech-debt-register.md` (append, only with user approval)
- Does NOT write to the code root or `tests/` under any circumstances
## Hard Constraints (Never Violate)
- Never sets `Status: Complete` without explicit user approval ("May I update the story file?")
- Never marks a Logic story Complete on a missing unit test when its resolved gate level is BLOCKING — the level is BLOCKING by default and whenever `testing.strict.logic` is `true`; it is ADVISORY only when `testing.strict.logic` is explicitly `false`
- Never marks an Integration story Complete on missing evidence (integration test file or playtest session log) when its resolved gate level is BLOCKING — BLOCKING by default and under `testing.strict.integration: true`; ADVISORY only under an explicit `false`
- Never marks a Visual/Feel or UI story Complete on a missing retained screenshot in `production/qa/evidence/` when its resolved gate level is BLOCKING — BLOCKING by default and under `testing.strict.visual`/`.ui: true`; ADVISORY only under an explicit `false`. A written description of a visual check is not a substitute for the image
- Never auto-fixes failing acceptance criteria — report failures and ask the user what to do
- Never proceeds to Phase 7 (file update) when the verdict is BLOCKED — list blockers and offer help resolving them
- Never silently overrides a BLOCKED verdict — if the user insists on advancing despite blockers, document the override risk explicitly in `## Completion Notes`
## Downstream Skill Expects
**Next skill:** `/gate-check` (Production → Polish gate, or end-of-sprint review)
- It will read: story files in `production/epics/` checking `Status: Complete` and presence of `## Completion Notes`
- It will read: `tests/unit/` and `tests/integration/` for test file presence and will run the test suite via Bash
- It assumes: every Complete Logic story whose `testing.strict.logic` gate resolved to BLOCKING (the default) has a corresponding test file that **exists**; under an explicit `testing.strict.logic: false` a Logic story may be Complete with the test gap documented in `## Completion Notes`
> **Existence, not passing.** `/story-done` checks that the test file exists with `Glob` and runs nothing, so a Complete Logic story may carry a test that fails. `/gate-check` running the suite is the FIRST point at which pass/fail is established, so it must never treat a Complete status as evidence the tests passed — it has to run them. That is also why an unconfigured test runner is `NOT ASSESSED` there rather than a silent skip: with no run, nothing upstream has verified the story either.
- It assumes: Visual/Feel and UI stories that are Complete have both an evidence doc and a retained screenshot in `production/qa/evidence/` — that gate is BLOCKING by default; only under an explicit `testing.strict.visual`/`.ui: false` may such a story be Complete with the deviation documented in `## Completion Notes`
- It will read: `production/sprint-status.yaml` for `status: done` entries if that file exists
## Known Fragile Points
- The story's `## Test Evidence` section must contain the exact file path written by `/dev-story`; a path mismatch causes a false BLOCKING result even if the test file exists under a slightly different name
- If `Type:` is absent from the story header, test evidence gate enforcement is ADVISORY only — stories can slip through without tests
- Manifest version staleness check is skipped if `docs/architecture/control-manifest.md` does not exist; the check being skipped is noted in the report but is non-blocking
- `production/sprint-status.yaml` update is silent (no separate approval); if that file uses a different key structure than expected, the update silently fails and sprint tracking becomes stale
- The traceability table escalation rule (>50% UNTESTED = BLOCKING) can be triggered by deferred Visual/Feel criteria that are legitimately not automatable — ensure deferred criteria are explicitly marked `DEFERRED` (not left as unchecked) to avoid false escalation
+266 -42
View File
@@ -1,12 +1,19 @@
---
name: story-done
description: "End-of-story completion review. Reads the story file, verifies each acceptance criterion against the implementation, checks for GDD/ADR deviations, prompts code review, updates story status to Complete, and surfaces the next ready story from the sprint."
description: "End-of-story completion review — verifies each acceptance criterion, checks GDD/ADR deviations, prompts code review, updates status."
argument-hint: "[story-file-path] [--review full|lean|solo]"
user-invocable: true
allowed-tools: Read, Glob, Grep, Bash, Write, Edit, AskUserQuestion, Task
disable-model-invocation: true
allowed-tools: Read, Glob, Grep, Bash, Write, Edit, AskUserQuestion, Agent
model: sonnet
---
!`source "${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/yaml-helper.sh" 2>/dev/null && resolve_config --keys review_mode,automation,workflow,story_granularity,qa.level,testing.strict,system_overrides`
Resolved above — use as-is; `--review` overrides `review_mode`. No block →
defaults in `.claude/docs/config-resolution.md`.
# Story Done
This skill closes the loop between design and implementation. Run it at the end
@@ -21,12 +28,37 @@ forgotten, and the story file reflects actual completion status.
## Phase 1: Find the Story
Resolve the review mode (once, store for all gate spawns this run):
1. If `--review [full|lean|solo]` was passed → use that
2. Else read `production/review-mode.txt` → use that value
3. Else → default to `lean`
See `.claude/docs/director-gates.md` for the full check pattern.
See `.claude/docs/director-gates.md` for the full check pattern. Individual gate definitions live in `.claude/docs/director-gates/[gate-id].md` — the spawned agent reads its own gate file; do not read it in the parent session.
Every `AskUserQuestion` call follows `.claude/docs/automation-modes.md`
(collaborative asks always · guided major-only · autonomous logs and proceeds;
`automation_always_ask` categories always prompt).
**Workflow tier**: resolved per the story's system (per
`.claude/docs/workflow-modes.md`) — **the GDD filename stem** of the story's
`GDD:` path (`design/gdd/<stem>.md` → `<stem>`), with the `[system]` segment of
its `TR-[system]-NNN` ID accepted only as a fallback alias: use the
`system_overrides` row for that system if the block lists one, else the
project value. It governs which Phase 4 deviation checks run — see Phase 4.
**Workflow companion — `modes.story_granularity`** (resolved above — supplied by
`modes.rigor` unless set explicitly): cadence expectation only — story-done fires **every 3–5 days** at
`coarse`, **every 1–2 days** at `balanced`, **multiple times/day** at `fine`. It
does not change any completion check.
**`qa.level`**: controls whether test *evidence is required*, where
`testing.strict` controls whether a failure blocks and `workflow` controls which
docs exist. `modes.rigor` sets `qa.level` and `workflow` together; set either
explicitly to vary it alone. `testing.strict` is not fronted by `rigor` at all.
At `minimal`, no evidence is required → skip the Test Evidence
Requirement check (Phase 3), the >50%-untested traceability escalation, and the
Phase 4b QA gate entirely; the acceptance-criteria verification still runs. At
`standard`, the story's own type requires evidence; at `full`, every type does.
`testing.strict` then decides whether present-but-failing evidence blocks.
(`rigor: minimal` sets both; `qa.level: minimal` on its own leaves the workflow
tier where it was.)
**If a file path is provided** (e.g., `/story-done production/epics/core/story-damage-calculator.md`):
read that file directly.
@@ -59,13 +91,19 @@ Read the full story file. Extract and hold in context:
- **Estimated vs actual scope** — if an estimate was noted
Also read:
- `docs/architecture/tr-registry.yaml` — look up each TR-ID in the story.
Read the *current* `requirement` text from the registry entry. This is the
source of truth for what the GDD required — do not use any requirement text
that may be quoted inline in the story (it may be stale).
- `docs/architecture/tr-registry.yaml` — grep the story's TR-IDs
(`Grep pattern="id: <each TR-ID>" path="docs/architecture/tr-registry.yaml" output_mode="content" -A 6`),
not a full read of the registry. Read the *current* `requirement` text from each
matched entry. This is the source of truth for what the GDD required — do not use any
requirement text that may be quoted inline in the story (it may be stale).
- The referenced GDD section — just the acceptance criteria and key rules, not
the full document. Use this to cross-check the registry text is still accurate.
- The referenced ADR(s) — just the Decision and Consequences sections
- The referenced ADR(s) — **just the `## Decision` and `## Consequences`
sections, never an unbounded full read.** Map headings first
(`Grep pattern="^## " path="docs/architecture/[adr-file].md" output_mode="content" -n`),
then bounded-`Read` only those two spans. This is the exact same content
Phase 4 item 3's ADR constraints check needs — hold it here, do not
re-read it there.
- `docs/architecture/control-manifest.md` header — extract the current
`Manifest Version:` date (used in Phase 4 staleness check)
@@ -82,7 +120,7 @@ three methods:
- **Test pass check**: if a test file path is mentioned, run it via `Bash`.
- **No hardcoded values check**: `Grep` for numeric literals in gameplay code
paths that should be in config files.
- **No hardcoded strings check**: `Grep` for player-facing strings in `src/`
- **No hardcoded strings check**: `Grep` for player-facing strings in the **code root** (resolve per `.claude/docs/code-root-resolution.md`). **If the code root is unresolved, report `NOT ASSESSED — code root unresolved` rather than zero hits.**
that should be in localization files.
- **Dependency check**: if a criterion says "depends on X", check that X exists.
@@ -118,6 +156,12 @@ For each acceptance criterion in the story:
- **Integration test**: check `tests/integration/` similarly
- **Manual confirmation**: if the criterion was verified via `AskUserQuestion`
above with a "Yes — passes" answer, count that as a manual test
- **Retained screenshot**: if the criterion names something on screen and a
retained image under `production/qa/evidence/[story-slug]/` shows it (the
`Run result: OBSERVED` from `/dev-story` Phase 6 step 4), count that as
covered — put the image path in the Test column. A visual criterion
verified by looking is not UNTESTED; without this row every UI story
reads as >50% untested and false-escalates.
2. Produce a traceability table:
@@ -126,10 +170,12 @@ For each acceptance criterion in the story:
|-----------|------|--------|
| AC-1: [criterion text] | tests/unit/test_foo.gd::test_bar | COVERED |
| AC-2: [criterion text] | Manual playtest confirmation | COVERED |
| AC-3: [criterion text] | — | UNTESTED |
| AC-3: [criterion text] | production/qa/evidence/[slug]/01-shop-open.png | COVERED |
| AC-4: [criterion text] | — | UNTESTED |
```
3. Apply these escalation rules:
3. Apply these escalation rules (skip entirely at `qa.level: minimal` — no
evidence is required, so untested criteria never escalate):
- If **>50% of criteria are UNTESTED**: escalate to **BLOCKING** — test
coverage is insufficient to confirm the story is actually done. The verdict
@@ -144,38 +190,118 @@ For each acceptance criterion in the story:
### Test Evidence Requirement
Based on the Story Type extracted in Phase 2, check for required evidence:
**First apply `qa.level` (resolved in Phase 1).** At `minimal`, no evidence is
required — skip this entire subsection (no gate from test evidence; the verdict
rests on acceptance-criteria verification alone). At `standard`, require evidence
for the story's own type. At `full`, require evidence for every story type. Only
when evidence is required does the `testing.strict` resolution below apply.
| Story Type | Required Evidence | Gate Level |
Based on the Story Type extracted in Phase 2, check for required evidence.
**Resolve the gate level for this story's type.** A gate level is either
BLOCKING (a gap prevents the COMPLETE verdict in Phase 6) or ADVISORY (a gap is
noted in the Completion Notes but does not block). Resolve it from the
`testing.strict` block **already resolved in the resolved-config block at the top of this skill** — not by reading
`project.yaml` yourself:
1. Map the Story Type to a `testing.strict` key — Logic→`logic`,
Integration→`integration`, Visual/Feel→`visual`, UI→`ui`, Config/Data→`config`.
Take `testing.strict.<key>` from that resolved block. If its value is `true`
(case-insensitive) → BLOCKING; if `false` → ADVISORY; `unset` → fall through.
2. Else read `testing.strict` as a plain boolean (legacy single-value form). If
its value is `true` → BLOCKING or `false` → ADVISORY, it applies to every type.
3. Else use the default in the table below.
> **Use that resolved block, never `project.yaml` directly.** `testing.strict.*` is
> on the `/settings --local` whitelist, so a developer can set
> `testing.strict.logic=false` in `project.local.yaml` for fast WIP commits —
> `effects-map.md` specifies exactly this ("stricter dev's local `/story-done`
> blocks earlier"). Reading `project.yaml` alone silently ignores that file: the
> setting is accepted, displayed by `/settings`, and has no effect. The
> `resolve_config` block at the top of this skill already merges local over base.
Only `true` and `false` (case-insensitive) are recognized at steps 1–2. A key
that is present but holds any other value — `maybe`, `1`, `yes`, etc. — is
treated as unset: continue to the next step, and surface the unrecognized value
to the user.
| Story Type | Required Evidence | Default Gate Level |
|---|---|---|
| **Logic** | Automated unit test in `tests/unit/[system]/` — must exist and pass | BLOCKING |
| **Logic** | Automated unit test in `tests/unit/[system]/` — must exist and pass (this skill verifies **existence**; see the note below Phase 3) | BLOCKING |
| **Integration** | Integration test in `tests/integration/[system]/` OR playtest doc | BLOCKING |
| **Visual/Feel** | Screenshot + sign-off in `production/qa/evidence/` | ADVISORY |
| **UI** | Manual walkthrough doc OR interaction test in `production/qa/evidence/` | ADVISORY |
| **Visual/Feel** | Retained screenshot + sign-off in `production/qa/evidence/` | BLOCKING |
| **UI** | Retained screenshot of each screen touched, in `production/qa/evidence/` | BLOCKING |
| **Config/Data** | Smoke check pass report in `production/qa/smoke-*.md` | ADVISORY |
The **Default Gate Level** column applies when `testing.strict` is unset (the
common case). When `testing.strict` is configured, the resolved value from
steps 1–2 overrides it. Visual/Feel and UI default to BLOCKING because for a
game the rendered result is the product; set `testing.strict.visual` or
`testing.strict.ui` to `false` for an advisory gate.
> **Exception — `/smoke-check`.** The ADVISORY default for **Config/Data** above
> governs *per-story evidence* gates, which is what this skill checks.
> `/smoke-check` is a build-health gate, not a per-story evidence gate, so its own
> unset default for `testing.strict.config` is **BLOCKING** — see
> `.claude/skills/smoke-check/SKILL.md` § "Resolve the gate enforcement level".
> The divergence is intentional; do not "fix" either side to match the other.
> **This phase checks that evidence EXISTS. It does not run anything.** The
> `Default Gate Level` table above, and `.claude/docs/coding-standards.md`, both
> say a Logic story's test "must exist **and pass**". The checks below establish
> only the first half — every one of them is a `Glob` or a `Grep`. A unit test
> that exists and fails, or that contains no assertions, satisfies them.
>
> Say which half you verified when you report. "Test file present at `<path>`"
> is the honest claim; "tests pass" is not one this phase can make. Pass/fail is
> established by `/gate-check` (runs the suite at a phase gate) and
> `/smoke-check` (runs it before QA hand-off), both of which do execute.
>
> Unlike `/regression-suite` and `/launch-checklist`, which stop at existence
> because their `allowed-tools` has no `Bash`, this skill HAS `Bash` — the limit
> here is the instruction, not the grant. Running the story's own test before
> closing it is a live option; it is not enabled because it needs a configured
> runner and a decision about what a missing runner should mean.
**For Logic stories**: first read the story's **Test Evidence** section to extract the
exact required file path. Use `Glob` to check that exact path. If the exact path is not
found, also search `tests/unit/[system]/` broadly (the file may have been placed at a
slightly different location). If no test file is found at either location:
- Flag as **BLOCKING**: "Logic story has no unit test file. Story requires it at
`[exact-path-from-Test-Evidence-section]`. Create and run the test before marking
this story Complete."
- Flag at the resolved gate level: "Logic story has no unit test file. Story
requires it at `[exact-path-from-Test-Evidence-section]`. Create and run the
test before marking this story Complete."
**For Integration stories**: read the story's **Test Evidence** section for the exact
required path. Use `Glob` to check that exact path first, then search
`tests/integration/[system]/` broadly, then check `production/session-logs/` for a
playtest record referencing this story.
If none found: flag as **BLOCKING** (same rule as Logic).
If none found: flag at the resolved gate level (same rule as Logic).
**For Visual/Feel and UI stories**: glob `production/qa/evidence/` for a file
referencing this story.
- If none: flag as **ADVISORY** — "No manual test evidence found. Create `production/qa/evidence/[story-slug]-evidence.md` using the test-evidence template and obtain sign-off before final closure."
- If found: read the file and check the sign-off table for unchecked boxes. Grep for lines matching `| .* | .* | .* | \[ \] Approved` (a sign-off row with an unchecked checkbox). If any unchecked sign-off rows are found: flag as **ADVISORY** — "Evidence file found at `[path]` but [N] sign-off(s) are still pending (shown as `[ ] Approved` in the sign-off table). Obtain required sign-offs before final closure. Note: for solo developers, all roles may be signed off by the same person."
- If all sign-off rows show `[x] Approved` or equivalent: note "Evidence file found and all sign-offs complete — ADVISORY passed."
**For Visual/Feel and UI stories**: glob `production/qa/evidence/` for both an
evidence doc referencing this story and a retained screenshot for it (`*.png`,
`*.jpg`, `*.gif`).
- If neither is found: flag at the resolved gate level — "No visual evidence found. Capture a screenshot of each screen or effect this story touched, save it under `production/qa/evidence/`, create `production/qa/evidence/[story-slug]-evidence.md` using the test-evidence template, and obtain sign-off before final closure."
- If the evidence doc exists but no screenshot is retained beside it: flag at the resolved gate level — "Evidence doc found at `[path]` but no screenshot is retained. A described check is an assertion, not evidence — capture the screen and save the image under `production/qa/evidence/` before final closure."
- If found: read the file and check the sign-off table for unchecked boxes. Grep for lines matching `| .* | .* | .* | \[ \] Approved` (a sign-off row with an unchecked checkbox). If any unchecked sign-off rows are found: flag at the resolved gate level — "Evidence file found at `[path]` but [N] sign-off(s) are still pending (shown as `[ ] Approved` in the sign-off table). Obtain required sign-offs before final closure. Note: for solo developers, all roles may be signed off by the same person."
- If the doc, the screenshot and all `[x] Approved` sign-off rows are present: note "Evidence doc and retained screenshot found, all sign-offs complete — gate satisfied."
The retained image **is** the `Run result: OBSERVED` from `/dev-story` Phase 6
step 4 (`.claude/docs/run-and-observe.md`); its absence means the run was
`NOT VERIFIED` or never happened, and the flag above is the consequence. The
run is not waived at `qa.level: minimal`.
**For every other story type**, read the `Run result:` line from the
`/dev-story` summary (the session extract in `production/session-state/active.md`,
or the story's `## Completion Notes`). `OBSERVED` with a retained path: note
it. `N/A — <reason>`: accept only if the reason names why nothing is
observable — "it's a Logic story" is not a reason. `NOT VERIFIED — <reason>`
on a story whose acceptance criteria name anything on screen: flag at the
resolved gate level for the story's type. No `Run result:` line at all: flag
as ADVISORY — "the implementation summary carries no run result; confirm the
build was launched and looked at before closure."
**For Config/Data stories**: check for any `production/qa/smoke-*.md` file.
If none: flag as **ADVISORY** — "No smoke check report found. Run `/smoke-check`."
If none: flag at the resolved gate level — "No smoke check report found. Run `/smoke-check`."
**If no Story Type is set**: flag as **ADVISORY** —
"Story Type not declared. Add `Type: [Logic|Integration|Visual/Feel|UI|Config/Data]`
@@ -189,6 +315,20 @@ Any BLOCKING test evidence gap prevents the COMPLETE verdict in Phase 6.
Compare the implementation against the design documents.
> **Workflow tier adjustment** (resolved in Phase 1, per the story's system).
> Checks 1 (GDD rules) and 3 (ADR constraints) below are the `full` baseline:
> - **`full`** — run both: full GDD traceability against the current TR text +
> the ADR constraints check.
> - **`standard`** — run the GDD rules check against the **5 required sections**;
> run the ADR constraints check only where a **critical ADR** governs the story.
> - **`minimal`** — **acceptance-criteria check only**: skip checks 1 and 3 (no
> GDD/ADR traceability expected). Checks 2 (manifest), 4 (hardcoded values), and
> 5 (scope) still run as written.
>
> This adjustment governs only the Phase 4 *deviation* checks. The test-evidence
> gates (Phase 3 traceability, Phase 4b QA coverage) are governed by `qa.level`
> and `testing.strict`, not `workflow` — they run independently of the tier here.
Run these checks automatically:
1. **GDD rules check**: Using the current requirement text from `tr-registry.yaml`
@@ -206,9 +346,10 @@ Run these checks automatically:
is v[current-date]. New rules may apply. Run /story-readiness to check.`
- If control-manifest.md does not exist → skip this check.
3. **ADR constraints check**: Read the referenced ADR's Decision section. Check
for forbidden patterns from `docs/architecture/control-manifest.md` (if it
exists). `Grep` for patterns explicitly forbidden in the ADR.
3. **ADR constraints check**: Use the ADR's `## Decision` section already
loaded in Phase 2 — do not read the ADR file again. Check for forbidden
patterns from `docs/architecture/control-manifest.md` (if it exists).
`Grep` for patterns explicitly forbidden in the ADR.
4. **Hardcoded values check**: `Grep` the implemented files for numeric literals
in gameplay logic that should be in data files.
@@ -229,12 +370,16 @@ For each deviation found, categorize:
## Phase 4b: QA Coverage Gate
**Skip this phase entirely at `qa.level: minimal`** (resolved in Phase 1) — no
test evidence is required, so there is no coverage to review. Note: "QL-TEST-COVERAGE
skipped — qa.level minimal." Proceed to Phase 5.
**Review mode check** — apply before spawning QL-TEST-COVERAGE:
- `solo` → skip. Note: "QL-TEST-COVERAGE skipped — Solo mode." Proceed to Phase 5.
- `lean` → skip (not a PHASE-GATE). Note: "QL-TEST-COVERAGE skipped — Lean mode." Proceed to Phase 5.
- `full` → spawn as normal.
After completing the deviation checks in Phase 4, spawn `qa-lead` via Task using gate **QL-TEST-COVERAGE** (`.claude/docs/director-gates.md`).
After completing the deviation checks in Phase 4, spawn `qa-lead` via `Agent` using gate **QL-TEST-COVERAGE** (`.claude/docs/director-gates/ql-test-coverage.md`).
Pass:
- The story file path and story type
@@ -266,7 +411,7 @@ Skip this phase for Config/Data stories (no code tests required).
- Record the answer in the completion notes (Phase 7). All three options proceed to Phase 6.
- `full` → spawn as normal.
Spawn `lead-programmer` via Task using gate **LP-CODE-REVIEW** (`.claude/docs/director-gates.md`).
Spawn `lead-programmer` via `Agent` using gate **LP-CODE-REVIEW** (`.claude/docs/director-gates/lp-code-review.md`).
Pass: implementation file paths, story file path, relevant GDD section, governing ADR.
@@ -314,21 +459,92 @@ Before updating any files, present the full report:
[All changes within stated scope] OR:
- Extra files touched: [list] — [note whether valid or scope creep]
### Verdict: COMPLETE / COMPLETE WITH NOTES / BLOCKED
### Verdict: COMPLETE / COMPLETE WITH NOTES / NOT ASSESSED / BLOCKED
```
**Verdict definitions:**
- **COMPLETE**: all criteria pass, no blocking deviations
- **COMPLETE WITH NOTES**: all criteria pass, advisory deviations documented
- **NOT ASSESSED**: one or more acceptance criteria could not be evaluated at
all — name which, and why
- **BLOCKED**: failing criteria or blocking deviations must be resolved first
If the verdict is **BLOCKED**: do not proceed to Phase 7. List what must be
fixed. Offer to help fix the blocking items.
**`NOT ASSESSED` — the story nobody could verify.** Rank: it **outranks COMPLETE
and COMPLETE WITH NOTES** (a review that could not evaluate a criterion has not
shown the criterion is met) and **ranks below BLOCKED** (a criterion known to
fail is more actionable than one nobody could check, and demoting it would bury
it). It is not a gentler BLOCKED: "this acceptance criterion fails" and "I could
not tell whether it passes" send the reader to different fixes.
**Verdict precedence — first matching rule wins**, evaluated in this order:
**BLOCKED**, then **NOT ASSESSED**, then **COMPLETE WITH NOTES**, then
**COMPLETE**. A run with both a failing criterion and an unassessable one is
BLOCKED. Stating the order mechanically, rather than leaving it to be inferred
from the rank sentence, is what keeps two reviewers from grading the same story
differently.
Emit it when any of:
- An acceptance criterion **cannot be evaluated at all** — it names no observable
outcome, so no evidence could settle it either way.
> **Not the same as Phase 3's `DEFERRED`.** A criterion that is evaluable but
> needs a playtest is `DEFERRED — requires playtest session`, it does **not**
> block, and Phase 3 keeps ownership of it. This trigger is for a criterion no
> session could ever settle as written. If Phase 3 already marked it DEFERRED,
> that classification stands and this trigger does not fire.
- The **test evidence is present but unreadable or unclassifiable** — corrupt,
empty, or of a type that cannot be determined.
> **Absent evidence is Phase 3's, not this trigger's.** Phase 3 resolves a
> missing file through `testing.strict`: BLOCKING types produce **BLOCKED**,
> ADVISORY types produce **COMPLETE WITH NOTES**. Both outrank or are already
> decided, so re-routing "absent" here would silently override an explicit
> advisory ruling. *Unreadable* is the genuinely unassessable case, and it is
> the only one this trigger claims.
- **`/test-evidence-review` returned `NOT ASSESSED`** for this story — applicable
only when that skill was actually run against it, which this skill does not do
itself. It
propagates: that skill's whole point is that "could not check" is not
"checked and fine", and collapsing its unknown into a COMPLETE here would undo
the distinction one skill downstream. `coding-standards.md` marks Logic and
Integration evidence BLOCKING, so this is the path where an unverifiable story
would otherwise acquire a verdict saying somebody verified it.
- A **deviation's severity cannot be determined** because the GDD or ADR it
would be judged against is missing.
A `NOT ASSESSED` verdict takes the same Phase 7 path as BLOCKED: do not
automatically proceed, list what could not be checked and what would make it
checkable. Closing anyway remains the user's explicit call, and stays gated by
Phase 7's `scope_changes` always-ask rule.
If the verdict is **BLOCKED**: do not *automatically* proceed to Phase 7. List
what must be fixed and offer to help fix the blocking items. This is the
default path, not an absolute stop — the user may still explicitly ask to
close the story anyway despite the blockers. That request is what routes to
Phase 7's menu below, and Phase 7's own `scope_changes` always-ask rule is
exactly what stands between that request and a silent close in autonomous
mode. Do not treat "do not automatically proceed" as "Phase 7 is now
unreachable" — it is reachable, on request, and gated when reached.
---
## Phase 7: Update Story Status
**Reached one of two ways**: normally, immediately after a COMPLETE or
COMPLETE-WITH-NOTES verdict in Phase 6; or, after a BLOCKED **or NOT ASSESSED**
verdict, only if the user explicitly asks to close the story despite the
blockers (Phase 6 does not advance here on its own in either case).
**Automation note**: This is the story-completion gate. Closing a story whose
verdict is BLOCKED (failing acceptance criteria) **or NOT ASSESSED** (criteria
nobody could evaluate) — the "Accept deviations as-is and close anyway" option —
is a `scope_changes` decision. Call
`is_always_ask_category scope_changes`; when it returns 0 (the default), this
gate prompts via `AskUserQuestion` **regardless of `modes.automation`** —
autonomous mode must NOT silently close a BLOCKED story, even when the user's
own request is what got you here. For a COMPLETE or COMPLETE-WITH-NOTES
verdict, autonomous mode may pick "Close the story (Recommended)" and record
it via `log_decision`.
Use `AskUserQuestion` before writing anything:
- Prompt: "Verification complete. How do you want to proceed?"
- Options:
@@ -370,7 +586,7 @@ If "Fix first": stop here and list what the user flagged. Do not write any files
```
Suggested commit:
git add [src/ and tests/ files changed during implementation] [story-file-path]
git add [code-root and tests/ files changed during implementation] [story-file-path]
git commit -m "feat: [story title] ([TR-ID])"
```
@@ -382,7 +598,7 @@ After updating the story file, silently append to
`production/session-state/active.md`:
## Session Extract — /story-done [date]
- Verdict: [COMPLETE / COMPLETE WITH NOTES / BLOCKED]
- Verdict: [COMPLETE / COMPLETE WITH NOTES / NOT ASSESSED / BLOCKED]
- Story: [story file path] — [story title]
- Tech debt logged: [N items, or "None"]
- Next recommended: [next ready story title and path, or "None identified"]
@@ -440,13 +656,21 @@ If no more stories are ready but Must Have stories are still In Progress (not Co
## Collaborative Protocol
**In `collaborative` mode (the default).** For `guided` and `autonomous` modes,
see `.claude/docs/automation-modes.md` — the rules below describe collaborative
behavior. The BLOCKED-override close (Phase 7) always prompts regardless of mode
(it's a `scope_changes` always-ask decision).
- **Never mark a story complete without user approval** — Phase 7 requires an
explicit "yes" before any file is edited.
- **Never auto-fix failing criteria** — report them and ask what to do.
- **Deviations are facts, not judgments** — present them neutrally; the user
decides if they are acceptable.
- **BLOCKED verdict is advisory** — the user can override and mark complete
anyway; document the risk explicitly if they do.
- **BLOCKED and NOT ASSESSED verdicts are advisory** — the user can override and
mark complete anyway; document the risk explicitly if they do. For NOT
ASSESSED, the documented risk is that the criterion was never evaluated, not
that it failed — record which criteria those were, so the gap is recoverable
later rather than closed over.
- Use `AskUserQuestion` for the code review prompt and for batching manual
criteria confirmations.