Write the eval of each slice of a business intent''s plan — the third play of the shaping loop (ADR 049: /shape-slice, then /shape-plan, then /shape-eval). Run as /shape-eval <intent-issue> (for example /shape-eval 788), after /shape-plan. For each slice the plan holds it reads only the epic (with its feature-to-story map), the stories in it and the slice''s hidden file, and writes, in the slice''s evals folder, one story file for each story (eval.<feature-id>.feature.md, Markdown with Gherki...
Installs into .claude/skills of the current project.
Are you the author of Shape Eval?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/intent-driven-ai-shape-eval)
---
name: shape-eval
position: both
argument-hint: "<business intent issue number>"
description: 'Write the eval of each slice of a business intent''s plan — the third play of the shaping loop (ADR 049: /shape-slice, then /shape-plan, then /shape-eval). Run as /shape-eval <intent-issue> (for example /shape-eval 788), after /shape-plan. For each slice the plan holds it reads only the epic (with its feature-to-story map), the stories in it and the slice''s hidden file, and writes, in the slice''s evals folder, one story file for each story (eval.<feature-id>.feature.md, Markdown with Gherkin, its Feature is the story) with a scenario for each goal and each expectation of the story and, for each failure condition of the story''s feature, a scenario that shows it does not happen; and one slice file (eval.<slice-id>.md) with the stories in build order, the surface kind, the time limit for each scenario and the pass rule. Every step is a sentence of the step library of the slice''s surface kind and every Then is exact, so the eval later runs with no agent and no score: it passes only when every scenario passes. What only the build will make is a name the usage guide maps; what cannot be tested yet is named with its reason. It never reads an ICE page, the usage guide, the code or the product model, and never runs the eval. An eval is a fixed yardstick: a later run only adds scenarios and never changes one. Nothing is committed before the product owner types yes at one pinned checkpoint, after a short summary (counts per story and the untestable items), never the eval itself. Position both: it opens its own change and lands it through commit-change, propose-change, check-change and merge-change, on the plan''s branch.'
user-invocable: true
---
# shape-eval
A person names a business intent whose plan `/shape-plan` made. /shape-eval writes, for each
slice the plan holds, the slice's **one eval**, in Gherkin, in the slice's evals folder
(`.garura/project/plans/<intent-issue>/evals/<slug>/`, beside the hidden file): a scenario for
each goal and each expectation of the slice's stories, and, for each failure condition in the
slice's hidden file, a scenario that shows it does not happen. It reads only the epic, its
stories and the hidden file. It never runs the eval: the eval runs later, after the build,
with no agent, through the usage guide. An eval is a fixed yardstick: a later run never changes
a scenario that is there.
The eval is written to be exact. Every step is a sentence of the step library of the slice's
surface kind (`standards/schemas/eval/steps-<kind>.yaml`), which a runner does with no agent;
every Then is an exact result a tester can see; there is no score — the eval passes only when
every scenario of every story passes. What only the build will make is a name, which the usage
guide maps later. The product owner sees a short summary, never the eval.
**Pipeline position: both.** start-change opens the change at the head (on the plan's
sub-branch); the end sequence commit-change → propose-change → check-change → merge-change
lands it.
## Compiled From
This play was compiled from the shape-eval ICE (`reference/ice.md`) by create-play (#798).
Intent defines constraints (C1–C16) and failure conditions (F1–F16); the expectation defines
success scenarios (S1–S8), a Done means (D1–D6, baked to `stop-condition.yaml`), and one
recovery entry per failure condition. To modify this play, update `reference/ice.md` and
recompile with edit-play.
Do NOT edit this file manually — it is a compiled artifact.
## Role
You are the orchestrator. You own the step order, the summary and the checkpoint. The play
reads no memory itself. `template-manager` hands out the two eval templates, the step
libraries, the approval prompt and the close templates. The `evals-engineer` agent checks that
the copies are complete and calls the skill `author-slice-eval`, which writes every scenario;
the agent passes the skill's drafts on as they are and never writes content. You run all
mechanical work through bundled scripts: the gather and the copies (`gather_eval.py`), the
input check (`check_inputs.py`), the placing and the yardstick (`place_eval.py`), the eval file
check (`check_eval.py`), the summary (`render_summary.py`), the checkpoint record and the commit
gate (`record_approval.py`), the write guard (`scoped_write_guard.py`) and the stop condition.
The shared reader is `eval_common.py`. The play never writes an eval file by hand, and never
runs one.
**Forbidden:** reading an ICE page, the usage guide, the code or the product model, or handing
the agent anything but the copies (C1); writing outside the slices' eval files (C2); a story file
that is not Gherkin, whose `Feature:` is not its story, or a Then that is not exact; a score
(C3); a step that tests from the inside (C4); a goal, expectation or failure condition with no
scenario and no Untestable line, or a scenario that covers something no story or hidden-file
entry says (C5); a recovery or handoff text in the eval (C6); changing or deleting a scenario
that was there (C7); an eval for a slice that is not ready, or a slice not named (C8); a commit
before the typed yes, a checkpoint skipped by config, the eval itself shown in place of the
summary, or a stop that leaves a file (C9); landing outside the change plays or on the wrong
branch (C10); a real person's name, or text a person cannot follow (C11); a step that is not a
sentence of the surface kind's step library, or an eval run by an agent (C12); a guessed address
or command in place of a name, or a name not listed (C13); no time limit, or a story that is not
in the slice (C14); a copy of the eval or the hidden file left in short-term memory after the run
(C15); a covered story line with no recorded text, or a moved line not named or its scenario
rewritten (C16).
**Agent boundaries:**
| Agent | Domain | Skills it uses | Phases |
|-------|--------|----------------|--------|
| `evals-engineer` | Check the copies are complete (each story copy has a Scope of work; the hidden-file copy has an entry for each story's feature; the step library of the surface kind and the two templates are there), call the skill, pass its drafts on as they are | `author-slice-eval` | Draft |
| `template-manager` (utility) | Hand out the eval templates, the step libraries, the approval prompt and the close templates (evidence file, delivery report, Next-line map) into short-term memory | — | Gather, Checkpoint, Close |
`evals-engineer` is the single **domain agent**; `template-manager` is the one utility agent.
The skill `author-slice-eval` was built for this play. The start and end sequence are injected
sub-plays; everything else is a script.
## Pre-flight
| Check | Constraint | Action on Failure |
|-------|-----------|-------------------|
| Resolve config and `stm_base` (`.garura/core/config.yaml`) | — | Hard halt |
| A business intent issue number was given (`/shape-eval <intent-issue>`, digits only); the plan's ID is that number | C8 | Hard halt — ask for the intent's issue number |
| The plan file exists (`.garura/project/plans/<intent-issue>.yaml`) | C8 | Hard halt — run `/shape-plan <intent-issue>` first |
| **Clean model tree** — after Step 0: `git status --porcelain -- .garura/product/product-os` is empty, so the guard, which also watches the product model, sees only this run | C2 | Hard halt (REC2) |
| **Clean plans tree** — after Step 0: `git status --porcelain -- .garura/project/plans` is empty, so the guard sees only this run's writes and an undo never takes back a person's own edits | C2, C9 | Hard halt (REC2) |
Resolve the facts with the bundled resolver, not by inference:
```
git branch --show-current
python3 scripts/preflight.py --play shape-eval --config .garura/core/config.yaml \
--branch "<current branch>"
```
`preflight.py` gives `stm_base` and `evidence_record`. The change's own issue comes from
start-change (Step 0), not from the branch name: the intent issue and the change issue are two
issues. After Step 0, with `<issue>` = the change issue:
```
python3 scripts/session_stamp.py --phase start \
--marker "{stm_base}<issue>/status/session-stamp-shape-eval.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
git rev-parse HEAD > {stm_base}<issue>/context/shape-eval/start-sha.txt
```
Names used below: `<id>` = `<intent-issue>` (the plan's ID), `<plans>` = `.garura/project/plans`,
`<plan-file>` = `<plans>/<id>.yaml`, `<working>` = `{stm_base}<issue>/context/shape-eval/`. The
stop condition reads `{stm_base}<issue>/`.
**Resume check:** if `{stm_base}<issue>/status/shape-eval.json` exists, resume — skip completed
steps, reset an in-progress step to pending, continue from the first incomplete one.
## Task DAG
Create ALL tasks right after pre-flight, before any domain work. The play owns this DAG.
```
[T0] start-change (injected — start) blockedBy: []
[T1] Hand out the templates and the step libraries blockedBy: [T0]
[T2] Gather the epic, the stories and the hidden file blockedBy: [T1]
[T3] Draft the eval (agent → skill), with the input check blockedBy: [T2]
[T4] Place the drafts; the yardstick blockedBy: [T3]
[T5] The eval file check (fix up to 3 rounds) blockedBy: [T4]
[T6] Guard the writes blockedBy: [T5]
[T7] The summary and the checkpoint (yes / change / stop) blockedBy: [T6]
[T8] Commit the eval files blockedBy: [T7]
[E1] commit-change (injected — end) blockedBy: [T8]
[E2] propose-change (injected — end) blockedBy: [E1]
[E3] check-change (injected — end) blockedBy: [E2]
[E4] merge-change (injected — end) blockedBy: [E3]
[T9] Scenario Validation blockedBy: [E4]
[T10] Close blockedBy: [T9]
```
Mark each task in progress before its step and completed right after its eval passes. No
runtime reordering. A fix round sends the run back to T3; a **change** at the checkpoint sends
it back to T3 after the undo; T3–T7 run again in order.
## Workflow
### Phase: Start (injected — D2 position: both, head)
**Step 0 — start-change** · Owner: `start-change` (sub-play) · Depends on: pre-flight
Run the start-of-pipeline member as a sub-play with `parent_run_id`. It resolves or creates the
change issue, cuts its branch (the plan's sub-branch, `<plan-file>`), and inits short-term
memory. Then assert the clean model tree and the clean plans tree (hard halt if dirty — REC2).
{ "play": "start-change", "parent_run_id": "<this run id>",
"inputs": { "title": "[TASK] shape-eval: write the eval for business intent #<intent-issue>" },
"outputs": { "result": "<working>start-change.json" } }
### Phase: Gather
**Step 1 — Hand out the templates and the step libraries** · Owner: `template-manager` · Depends on: Step 0
{ "task": "load these templates into the issue's short-term memory",
"inputs": { "issue": <issue>,
"templates": ["standards/templates/eval-story.feature.md",
"standards/templates/eval-slice.md",
"standards/rules/eval-format.md",
"standards/schemas/eval/steps-*.yaml",
"standards/templates/approval-prompt.md"] },
"outputs": { "out_dir": "<working>templates/",
"manifest": "<working>templates/manifest-shape-eval.json" },
"task_id": "shape-eval-templates" }
`<libraries>` = each `steps-<kind>.yaml` copy the manifest lists. When a template is
`template_missing`, close HALTED and say which (the install lays the standards).
**Step 2 — Gather the epic, the stories and the hidden file** · Owner: play (script) · Depends on: Step 1
```
python3 scripts/gather_eval.py --plan-file <plan-file> --plans <plans> \
$(each of <libraries> as --library <path>) --working <working> --out <working>inputs.json
```
For each epic node of the plan file (`represents: <slice id>`) it reads the epic and its map
from the tracker, each story of the map, and the slice's hidden file (found by the slice id of
its first heading, in the slice's home plan: the plan of its first intent, which need not be
this plan; the eval is read and written in that same folder), and copies them into `<working>inputs/`, with the eval files the slice
already has in `<working>inputs/existing/<slug>/`. It never opens an ICE page, the usage guide,
the code or the product model (C1). A slice with no epic, no map, a story with no Scope of work,
no hidden file, no hidden-file entry for a story's feature, or no step library for its surface
kind is `ready: false` with its `reason` (C8, C12). Exit 2: no plan file or no epic node — tell
the person to run `/shape-plan <intent-issue>` first, and close HALTED.
**SE-1 (F1/C1):** every file in `<working>inputs/` is `epic-<n>.md`, `story-<n>.md`,
`hidden-<slug>.md` or under `existing/`; `inputs.json` names no path with `feature.md`,
`capability.md`, `ice.md`, `product-os` or `guides/`.
**SE-2 (F8/C8/C12):** each slice with `ready: false` has a `reason` that is not empty; each slice
with `ready: true` has a story for each line of its epic's map, each with a Scope of work, a
hidden-file entry for each story's feature, and a `surface_kind` with its `library`.
### Phase: Draft
**Step 3 — Draft the eval** · Owner: play (script), then `evals-engineer` · Depends on: Step 2
Write the contract below to `<working>contract.json` first, and run the input check on it; any
exit but 0 stops here and the agent is not dispatched (REC1):
```
python3 scripts/check_inputs.py --contract <working>contract.json --working <working> \
--out <working>inputs-check.json
```
{ "task": "have the eval of each ready slice drafted. Check the copies are complete before you call the skill: each story copy of each ready slice in inputs.json has a Scope of work, the hidden-file copy has an entry for each story's feature, the step library of the slice's surface kind and the two templates are given; if one is missing, stop with step_failure and name it. Call author-slice-eval with these inputs and, on a fix, the problems of eval-check.json, or, on a re-run, the product owner's change. Pass its drafts folder on as it is. Read only the copies; never open an ICE page, the usage guide, the code, the product model or the plans folder. Gather and pass on only; the skill writes every scenario",
"inputs": { "inputs": "<working>inputs.json", "copies": "<working>inputs/",
"library": "<libraries>",
"templates": ["<working>templates/.../eval-story.feature.md", "<working>templates/.../eval-slice.md",
"<working>templates/.../eval-format.md"],
"problems": "<working>eval-check.json, on a fix", "change": "<working>change.md, on a re-run" },
"outputs": { "drafts": "<working>drafts/" },
"skills": ["author-slice-eval"],
"task_id": "shape-eval-draft" }
**SE-3 (F1/C1):** `inputs-check.json` reads `ok: true`, written before each dispatch: every
input of the contract is inside `<working>` and names no ICE page, no product model, no usage
guide and no plans folder; every output is inside `<working>drafts/`.
**SE-4 (F2/C2):** the agent's reply names only `<working>drafts/`, and no file outside
`<working>` changed in the dispatch (`git status --porcelain -- <plans>` is empty after it).
### Phase: Place
**Step 4 — Place the drafts; the yardstick** · Owner: play (script) · Depends on: Step 3
```
python3 scripts/place_eval.py --inputs <working>inputs.json --drafts <working>drafts \
--out <working>place.json --yardstick-out <working>yardstick-check.json
```
For each ready slice it writes a story file the slice does not have as drafted; on a story file
the slice has, it keeps every line and adds only the drafted scenarios and Untestable lines for
what the file does not cover yet; it tags the story file of a feature that left the slice
`@out-of-slice` and keeps it; it writes the slice file unless what it says is the same as the
file the slice has (C7). A file with nothing to add is not touched. A placing again (after a fix)
first undoes the earlier one. Exit 1: the yardstick does not hold — a scenario that was there
changed or went away — put it back (REC7) and run Step 4 again. A slice that is not ready gets
nothing (C8).
**SE-5 (F7/C7):** `yardstick-check.json` reads `ok: true`: every scenario of every story file the
slice had before the run is in that file now, word for word; a story file whose feature left
the slice is still there, with `@out-of-slice`.
**SE-6 (F2/C2):** every path in `place.json` `files` is `<plans>/<id>/evals/<slug>/eval.<feature
id>.feature.md` or `<plans>/<id>/evals/<slug>/eval.<slice id>.md` of a ready slice.
### Phase: Check
**Step 5 — The eval file check** · Owner: play (script) · Depends on: Step 4
```
python3 scripts/check_eval.py --inputs <working>inputs.json --place <working>place.json \
--out <working>eval-check.json
```
It reads the eval files and checks, for each ready slice, every rule of the format (C3–C6,
C12–C14). Exit 1 lists each problem by file: send `eval-check.json` back to the
`shape-eval-draft` contract as `problems` (Step 3, with the input check again), then run Steps 4
and 5 again — at most three fix rounds. After the third round with problems, close HALTED and
show the problems to the person. It never runs the eval.
**SE-7 (F3/C3):** each story file parses as Markdown with Gherkin with one `Feature:` that reads
`Story #<n> — ...` and the tag `@story-<n>` above it; each scenario has a Then; the slice file
names the slice and its epic, the surface kind, a time limit and the pass rule, and lists the
epic's map in its order.
**SE-8 (F4/C5):** every goal, expectation and failure condition of each story is covered by a
scenario tag or an Untestable line with a reason (`eval-check.json` lists none uncovered).
**SE-9 (F5/C4/C5):** every scenario tag is a `@goal-<k>` or `@expectation-<k>` of its story or an
`@F<n>` of its feature's hidden-file entry; every step is a library sentence, so no step reads
the code or calls the product's parts.
**SE-10 (F6/C6):** no story file holds "the builder can fix this alone", "a person must decide",
"Way back:", "Who fixes:" or a recovery line of the hidden file.
**SE-11 (F12/C12):** every step of every scenario and Background is a sentence of the step library
of the slice's surface kind; no slice with no library got a file.
**SE-12 (F13/C13):** every [name] the story files use is listed under the slice file's Names, and
the list holds no other name.
**SE-13 (F14/C14):** the slice file's time limit is a whole number of seconds, more than 0, and the
folder holds no story file for a story that is not in the slice (besides those out of the slice).
**SE-14 (F8/C8):** no slice with `ready: false` got a file in this run (`place.json` `new_files`).
**SE-19 (F16/C16):** every goal and expectation a scenario covers has a "Lines covered" line with the
story line's exact text; on a story file the slice had before the run, each recorded line that no
longer matches the story is in `eval-check.json` `moved` (with its scenarios, was and now), and is
not a problem; `yardstick-check.json` reads `ok: true`, so those scenarios are word for word as
they were.
**Step 6 — Guard the writes** · Owner: play (script) · Depends on: Step 5
```
python3 scripts/scoped_write_guard.py --product-base .garura/product/ --base-ref HEAD \
--also-tree .garura/project/plans \
$(each path in place.json "files", as --allow '<path>') \
--out <working>guard-report.json
```
The allowed set is C2's: the eval files `place.json` lists. Under `<plans>` and in the product
model any other change is a violation. The run writes nothing in the product model, so it makes
no `feat(model)` commit. Exit 1: re-run with `--restore` to undo the writes outside
the set (REC2), then run Steps 4–6 again.
**SE-15 (F2/C2):** `guard-report.json` reads `ok: true` — no path in the product model and no path
under `<plans>` outside the eval files of `place.json` changed.
### Phase: Checkpoint (class: one-way-door, pinned)
**Step 7 — The summary and the checkpoint** · Owner: play (+ `template-manager`'s approval prompt) · Depends on: Step 6 · Checkpoint (class: one-way-door, **pinned** — the product owner decides; never skipped by config, `standards/rules/gate-config.md`)
```
python3 scripts/render_summary.py --check <working>eval-check.json --place <working>place.json \
--inputs <working>inputs.json --out <working>summary.md
```
The summary is built from the run's records: for each slice one plain sentence (what the eval
checks, how many scenarios and failure conditions, how many items it cannot test yet, how to read
the table), one line per story (Scenarios, Goals, Expectations, Failure conditions, Untestable),
each untestable item in one line with its reason, each scenario whose story line moved or changed
(for the person to judge, C16), each slice that is not ready with its reason, and where the files
are; on a later run, what the run adds. It never holds the eval itself (C9). It is never edited by hand. Frame it with
the approval prompt and show it. Ask the product owner to type **yes**, **change: <what>**, or
**stop**. Save the reply word for word to `<working>reply.md`, and record it:
```
python3 scripts/record_approval.py --decision <yes|change|stop> --reply "<their exact words>" \
--at <now, UTC> --out <working>approval.json
```
- **yes** → Step 8.
- **change** → undo this round's files (`python3 scripts/place_eval.py --undo <working>place.json
--inputs <working>inputs.json`), write the change to `<working>change.md`, and run Steps 3–7
again with it; show the summary again.
- **stop** → undo every file of the run and clear the copies (`python3 scripts/place_eval.py
--undo <working>place.json --clear <working>clear.json --inputs <working>inputs.json`), write
`git status --porcelain -- <plans>` to `<working>stop-check.txt`, and close HALTED. Nothing is
committed.
**SE-16 (F9/C9):** `approval.json` holds the product owner's typed words; `summary.md` has one
line per story of each ready slice and no `Scenario:` line; no commit after `start-sha.txt`
touches `<plans>` unless `approval.json` reads `decision: yes`; after a stop, `stop-check.txt` is
empty.
**SE-17 (F11/C11):** `summary.md` and every eval file use personas and plain words and name no
real person — `grep -i "$(git config user.name)"` over them finds nothing.
### Phase: Commit
**Step 8 — Commit the eval files** · Owner: play · Depends on: Step 7 (yes)
The commit runs only when the recorded answer is yes and `place.json` lists a file; the gate runs
right before it:
```
python3 scripts/record_approval.py verify --file <working>approval.json
git add -- $(each path in place.json "files")
git commit -m "feat(eval): write the eval of business intent #<id>'s slices (#<issue>)"
python3 scripts/place_eval.py --clear <working>clear.json --inputs <working>inputs.json
```
The clear runs right after the commit (C15): it deletes the drafts, the hidden-file copy and the
copies of the slice's eval files from before the run, so the run's records that the end sequence
commits hold no eval text. Exit 1 lists what is left: delete it and run the clear again (REC15).
**SE-20 (F15/C15):** `clear.json` reads `ok: true` with an empty `left`; `<working>drafts/`,
`<working>inputs/existing/` and every `<working>inputs/hidden-<slug>.md` are gone before Step E1.
### Phase: End sequence (injected — D2 position: both, tail)
Each member runs as a sub-play with `parent_run_id`; each resolves its own context from the
branch and config.
**Step E1 — commit-change** · blockedBy: Step 8 — commits the run records under `{stm_base}<issue>/`.
{ "play": "commit-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "<working>end/commit-change.json" } }
**Step E2 — propose-change** · blockedBy: E1
{ "play": "propose-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "<working>end/propose-change.json" } }
**Step E3 — check-change** · blockedBy: E2 — a reject stops the chain before merge.
{ "play": "check-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "<working>end/check-change.json" } }
**Step E4 — merge-change** · blockedBy: E3 — lands on the plan's branch.
{ "play": "merge-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "<working>end/merge-change.json" } }
**SE-18 (F10/C10):** `start-change.json` shows the change opened by start-change; the
`<working>end/` results are present in order; the PR's base is the plan's branch, and
`merge-change.json` reports it merged.
### Phase: Scenario Validation
**Step 9 — Scenario evals** · Owner: play · Depends on: E4
- **SCE-1 (S1 — product owner, first eval):** `eval-check.json` reads `ok: true`; the slice's
evals folder holds one story file for each story of the epic's map and the slice file, and the
slice file lists them in the map's order.
- **SCE-2 (S2 — product owner, run again, nothing changed):** `place.json` `files` is empty and
each eval file's `shasum -a 256` equals its copy in `<working>inputs/existing/<slug>/`.
- **SCE-3 (S3 — product owner, the slice changed):** `yardstick-check.json` reads `ok: true`; the
new story has its file; the story file of the feature that left is still there with
`@out-of-slice`; the slice file lists it under "Out of the slice".
- **SCE-4 (S4 — product owner, a slice not ready):** `inputs.json` names the slice with
`ready: false` and its reason, `summary.md` names it, and its evals folder got no file.
- **SCE-5 (S5 — product owner, stop):** `approval.json` reads `decision: stop`, `stop-check.txt`
is empty, and no eval file of the run is left.
- **SCE-6 (S6 — product owner, something not testable yet):** `eval-check.json` reads `ok: true`
and lists the goal under the story's `untestable` with its reason; `summary.md` shows it.
- **SCE-7 (S7 — product owner, a surface with no step library yet):** `inputs.json` names the slice
with `ready: false` and "no step library", `summary.md` names it, and its evals folder got no
file.
- **SCE-8 (S8 — product owner, a story line changed):** `eval-check.json` lists the scenario of the
changed line under `moved`, `summary.md` names it, and `yardstick-check.json` reads `ok: true`.
### Phase: Evidence & Close
**Step 10 — Close** · Owner: play · Depends on: Step 9
Run the Standard Play Close. The close is stop-condition gated: the baked `stop-condition.yaml`
(D1 the eval file check reads ok; D2 the approval reads yes; D3 the input check reads ok; D4 the
yardstick check reads ok; D5 the write guard reads ok; D6 the copies are cleared) must read **held** before a COMPLETED
close. A stop, or a run with no ready slice, closes HALTED.
```bash
# --- Standard Play Close (shared; see ../_shared/play-close.md) ---
# Run the close in ../_shared/play-close.md. Its Step L loads the templates through
# template-manager and sets ts and evidence_dest; then run this play's own lines below;
# then Steps C0-C2.
# evidence_base, slug (project-scoped play):
# evidence_base="${stm_base}${issue}/evidence/shape-eval/" ; slug="#${issue}"
# Session identity stamp (#463) — close phase; start phase ran at pre-flight
session_stamp=$(python3 scripts/session_stamp.py --phase close \
--marker "${stm_base}${issue}/status/session-stamp-shape-eval.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Evaluate the Done means against the issue folder.
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}${issue}/" \
--out "${stm_base}${issue}/status/stop-condition-shape-eval.yaml"
sc_exit=$? # 0 held · 1 unmet · 2 error
```
**Step C0 — bind the verdict.** `sc_exit == 0` (held) permits `status: COMPLETED`. Anything else
closes `HALTED` with `exit_reason: stop_condition_unmet`, and the evidence's Stop Condition
section names every unmet clause. An unevaluable verdict is never a pass.
**Step C1 — Write evidence file.** Gated by the resolved `evidence.record` flag (per-play
`evidence.plays.shape-eval`, else global; absent ⇒ record). When false, skip the write and record
`evidence skipped (record=false)` in the report's pointer line. Otherwise fill the
`evidence-file.md` slots (play `shape-eval`, run_id `shape-eval-${ts}`, issue, the intent issue,
started_at/completed_at, status per C0, exit_reason; artifacts produced: `inputs.json`,
`inputs-check.json`, the drafts, `place.json`, `yardstick-check.json`, `eval-check.json`,
`guard-report.json`, `summary.md`, `approval.json`, `clear.json`, each eval file written, the eval commit sha,
and the skill the agent contract named; step and scenario eval results SE-1…SE-20 /
SCE-1…SCE-8; the checkpoint answer in the person's words; the session identity stamp fields from
$session_stamp (#463); and stop_condition per C0) and write to `$evidence_dest`. Do NOT
hand-author the body.
**Step C2 — Render delivery report.** Fill the `delivery-report.md` slots and output the report:
`## shape-eval Delivered — #${issue}`, the Run Summary table, the Pipeline Steps table from the
task DAG, the Artifacts Produced table (each eval file, each slice not ready with its reason, the
PR), and the **Next** line from the Next-line map (`$next_map`, from `template-manager`), per
`play-close.md`; omit it when the map's command is null. Next Steps: the build is given the
stories, never the eval (#898); the eval runs after the build, with no agent, through the usage
guide. End with a pointer to `$evidence_dest`. Always emitted; never gated.
```bash
# --- end Standard Play Close ---
```
## Scenario Validation
| Scenario | Persona | Eval |
|----------|---------|------|
| S1 — first eval | product owner | SCE-1 |
| S2 — run again, nothing changed | product owner | SCE-2 |
| S3 — the slice changed | product owner | SCE-3 |
| S4 — a slice not ready | product owner | SCE-4 |
| S5 — stop at the checkpoint | product owner | SCE-5 |
| S6 — something not testable yet | product owner | SCE-6 |
| S7 — a surface with no step library yet | product owner | SCE-7 |
| S8 — a story line changed | product owner | SCE-8 |
## Recovery
| For | Trigger | Direction | Handoff |
|-----|---------|-----------|---------|
| F1 | the run read an ICE page, the usage guide, the code, or any file other than the epic, its stories and the hidden file | throw away what was made from the wrong input, and make it again from the copies only | autonomous |
| F2 | an eval was written outside the slice's evals folder, or the run wrote another file outside its own records | undo each write outside the slice's eval files (`scoped_write_guard.py --restore`) | autonomous |
| F3 | a story file is not Gherkin a tool can read, its `Feature:` is not its story, a Then is not exact, or the slice file does not list every story in build order | write the story file again as Gherkin with its story as its `Feature:` and an exact Then, or write the slice file's list again in build order | autonomous |
| F4 | a goal, an expectation or a failure condition has no scenario and no Untestable line | add the missing scenario, or the Untestable line with its reason | autonomous |
| F5 | a scenario covers nothing a story or the hidden file says, or tests from the inside | take the scenario out, or write it again from the outside, on the surface | autonomous |
| F6 | the eval holds handoff text or a recovery | take out the handoff text or the recovery | autonomous |
| F7 | a later run changed or deleted a scenario that was there | put back each scenario that was there, word for word, and add only what is new | autonomous |
| F8 | a slice with no epic, stories or hidden file got an eval, or the run did not name it | take back the eval of the slice that is not ready, and name it in the summary with its reason | autonomous |
| F9 | a commit before the yes, or a stop that left a file | take the commit back, or undo the writes the stop left (`place_eval.py --undo`); show the summary again | human |
| F10 | the change landed outside the change plays, or on the wrong branch | land the change through the change plays, on the plan's branch | human |
| F11 | a page names a real person, or a person cannot follow it | rewrite with personas and plain words | autonomous |
| F12 | a step is not a sentence of the step library of the slice's surface kind, or a slice with no library got an eval | write the step again with a library sentence; when the surface kind has no library, take back its eval and name it | autonomous |
| F13 | a step holds a guessed address, command or field in place of a name, or a name is not listed | put a name in place of the guess, and list each name in the slice file | autonomous |
| F14 | the slice file has no time limit, or the eval covers a story that is not in its slice | set the time limit in the slice file, and take out the story that is not in the slice | autonomous |
| F15 | a copy of the eval or the hidden file stays in the run's short-term memory after the run | delete the drafts, the hidden-file copy and the copies of the eval files (`place_eval.py --clear`), and from the records commit when they were committed | autonomous |
| F16 | a story file does not record the line text its scenarios cover, or a later run does not name a scenario whose story line moved or changed, or rewrites it | write the Lines covered list with each covered line's exact text; for a line that moved or changed, put back the scenario as it was and name it in the summary | autonomous |
## Pause and Resume
Steps run top to bottom. On entry, resolve the change issue from start-change's result, check the
status marker, skip completed steps, reset an in-progress step to pending, and continue. Nothing
is committed before Step 8. A resume after Step 4 started reads `place.json`: Step 4 undoes it
before it places again, so a file is never placed twice. A run that closed HALTED at a stop has
nothing to resume: its files were undone and its copies cleared.
## Compilation Metadata
| Field | Value |
|-------|-------|
| fingerprint | sha256:ae979989ab9dfe99b8ab030501564f38db4d43c4ac41c99b3fdbdf4da0ef67a8 (of `reference/ice.md`) |
| compiled_by | create-play (#798); edit-play (#798 round 2: C15, C16) |
| pipeline_position | both (start-change head; commit → propose → review → merge close) |
| workflow_structure | A (gather the copies; draft; place with the yardstick; check; one summary at one pinned one-way-door checkpoint; nothing committed before the yes; stop-condition gated close) |
| stop_condition | stop-condition.yaml (D1–D6), gate live at Step C0 |
| domain_agents | 1 (evals-engineer) |
| utility_agents | 1 (template-manager — the eval templates, the step libraries, the approval prompt and the close) |
| skills_used | author-slice-eval (built for this play) |
| skills_missing | none |
| tracker_verbs | view-issue (read only), through platform_adapter.py |
| standards_consumed | standards/templates/eval-story.feature.md, standards/templates/eval-slice.md, standards/rules/eval-format.md, standards/schemas/eval/steps-<kind>.yaml (command-line first), standards/templates/approval-prompt.md, standards/rules/gate-config.md |
| scripts | preflight.py, session_stamp.py, check_stop_condition.py, platform_adapter.py, scoped_write_guard.py (copied unchanged from create-play references); record_approval.py (from /shape-slice); eval_common.py, gather_eval.py, check_inputs.py, place_eval.py, check_eval.py, render_summary.py (this play) |
| tests | test_shape_eval_scripts.py |
| step_evals | 20 (SE-1…SE-20) |
| scenario_evals | 8 (SCE-1…SCE-8) |
| recovery_entries | 16 (one per failure condition; 14 autonomous / 2 human) |