Skip to content
Back to skills

Grill

ASecurity

Cut one REALIZED slice into user-testable delivery epics — the handoff from the product model to the delivery pipeline. /roadmap says which slice to build; the seven realize lenses have solved its design and /measure has stamped it realized — the marker /grill requires before it cuts. Each epic is a meaningful increment a user can open, exercise, and verify when delivered (the user-testability grain — never internal-only work), self-contained, referencing the slice''s intent and lenses, never...

  • 3 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added September 10, 2026
businesspythongoshellbashnodeawsgitapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 13 files and shows the line behind each finding

Scanned September 10, 2026

npx -y skills add kapilvirenahuja/garura --skill grill --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Grill?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Grill
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kapilvirenahuja-grill/badge)](https://www.skillsdirectory.com/skills/kapilvirenahuja-grill)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: grill
position: both
description: 'Cut one REALIZED slice into user-testable delivery epics — the handoff from the product model to the delivery pipeline. /roadmap says which slice to build; the seven realize lenses have solved its design and /measure has stamped it realized — the marker /grill requires before it cuts. Each epic is a meaningful increment a user can open, exercise, and verify when delivered (the user-testability grain — never internal-only work), self-contained, referencing the slice''s intent and lenses, never copying them. The cut is GRILLED before it is written — grilling draws the box from realized slice to delivery epics so nothing drifts outside the declared intents: every challenge cites a specific declared item (never taste), questions are asked plainly ONE AT A TIME with no recommendations attached, a tension closes only on the typed human answer (the record carries the push-back shown, the human''s words, and the human-derived directive or reason — the agent never self-resolves), and an unresolved delivery-method choice becomes a cited decision question, never an assumption. Lens defects route back to their lens play. Ordered, every functionality covered or explicitly deferred. Direct-model-write (ADR 026, write-then-review): the cut is written to the live model first (each epic.md by author-epics, the spine epics index by the keyed persist), guarded once, then reviewed — nothing is COMMITTED before the checkpoint resolves, and cancel reverts the written tree. Writes only epics; opens no implementation issue — /start picks epics up one at a time. Position BOTH (#437, decision 24): /grill is self-contained — start-change opens its own branch at the head, the approved cut is committed as a feat(model) model delta, and the standard end sequence (commit-change → propose-change → review-change → merge-change) raises, reviews, and lands it — no manual /commit-change.'
user-invocable: true
---

# grill

Take one **realized** slice — a vertical product increment whose design the seven realize
lenses (quality → ux → agentic → arch → measure → run) have already solved, stamped
`status: realized` by /run — and cut it into **epics**: the ordered, user-testable delivery
increments the delivery pipeline picks up. /grill sits below /roadmap (which says which
slice to build next) and above /start (which opens one issue per epic). Epics are
permanent: kept on the slice, stamped `delivered` on merge and kept in place as the
product model's as-delivered record (ADR 019).

The **grain of an epic is user-testability**, not the functionality: when an epic is
delivered, a user can open the product, do something, and see it work. An epic may thread
several of the slice's functionalities; a functionality may yield several epics; internal
machinery rides inside the epic that surfaces it, never alone. Every epic is
self-contained — context (persona, systems, scope), acceptance criteria naming observable
user behavior, and the one-line `user_check` — and **references** the slice's intent
records and lenses, never copies them.

The cut is **grilled, not relayed** — and grilling is **drawing the box**: going from A
(the realized slice — its declared intents, lenses, profile) to B (delivery epics) must
have no drift, and the declared intents are the box the cut cannot step outside. The play
pushes back steelman-hard — thin acceptance, untestable increments, tensions between the
cut and what the slice declared — and every push-back cites the specific declared item it
defends. A tension is resolved in the cut or explicitly accepted with a recorded reason; a
defect in a lens itself is recorded and routed back to that lens's play, never patched
here. The full cut persists atomically, only after the human approves it.

**The grilling is a conversation with the human, and the record proves it (#436).** The
play builds its question list for the round — push-backs and open delivery-method
questions — and asks **one question at a time, simply stated**: the citation and the
question, nothing else. No recommendations, no option menus. A tension leaves `live` only
on the typed answer; the round record carries the push-back actually shown, the human's
own words, and the directive or reason derived from them. The agent revising the draft on
its own never closes a tension. And where the box has no wall — an epic's user check
depending on a delivery method the lenses never decided — the play asks a cited decision
question; only the human draws that wall.

**Pipeline position: both (#437, decision 24; confirmed by Kapil).** /grill is
SELF-CONTAINED: it opens and closes its own branch, like fix-bug. The realize work lands
through its own three pipes — the FUNCTIONAL pipe (ux start → agentic → marketing end), the
NON-FUNCTIONAL pipe (architecture start → quality → run end), and the DELIVER pipe (measure),
each merging to main — so /grill no longer rides a branch another play opened. The D2 pipeline-position rule injects
`start-change` at the head (resolve or create the grill issue, cut the branch off fresh
main, optional worktree, init STM) and keeps the close sequence `commit-change →
propose-change → review-change → merge-change` at the tail: after the epics are persisted
and verified, the locked cut is committed, raised, reviewed, and merged without the human
having to remember `/commit-change`. The bracketed change is the MODEL change only; /grill
still opens no implementation issue and cuts no branch for an epic's delivery work —
picking up an epic remains /start's job, one epic at a time. /grill works on one slice and
remains the slice's end conceptually: nothing about a slice is finished until grill is.

**Write discipline (ADR 026, `standards/rules/direct-model-write.md`).** The LLM author-epics
skill writes ONLY the per-node docs (each `epic.md`, and the slice's `deferrals.yaml`)
straight to the live model; the one shared-file mutation — the spine `_spine.yaml` `epics`
index — is done by the deterministic keyed persist script (`persist_epics.py`), in place,
keyed to the target slice so it cannot touch an epic delivery already owns (status not
`ready`) or any spine field outside `epics`. There is no `draft/` copy and no apply/promote
step. Order is **write-then-review** (ADR 026): the full cut — the LLM's `epic.md` docs AND
the keyed persist's `epics`-index entries — is written to the live model FIRST, then guarded
once, so the checkpoint presents the real model git diff; nothing is COMMITTED before the
gate resolves. Containment is the post-write scoped guard (`scoped_write_guard.py`), not a
draft. The model tree is clean at entry — the fresh branch the injected `start-change` head
cuts off main guarantees it (F19) — and the play commits its own `feat(model)` model delta
after the approved checkpoint (C18), so the working-tree diff vs HEAD is exactly this run's
delta. On checkpoint cancel the whole written delta is reverted (`git restore` + `git clean`).

## Compiled From

This play was compiled from the grill ICE (`reference/ice.md`) by play-creator and
recompiled via play-editor (#436, #437; closed-schema round-report fix; #466 Batch C —
gated close per ADR 025; #500 — migrated to direct-model-write per ADR 026 and
`standards/rules/direct-model-write.md`). Intent defines constraints (C1–C18) and failure
conditions (F1–F19); the expectation defines success scenarios (S1–S8), a Done means
(D1–D7, baked to `stop-condition.yaml`), and one recovery entry per failure condition.
To modify this play, update `reference/ice.md` and recompile with play-editor.
Do NOT edit this file manually — it is a compiled artifact.

## Role

You are the orchestrator. You own the workflow, the step order, the grilling rounds (the
push-back authoring from the cited tensions the tension-check skill returns, and every
exchange with the human), and the checkpoint. You delegate the two pieces of judgment —
drafting/revising the cut and detecting tensions against the declared design — to the
`product-os-keeper` agent via JSON contracts over files on disk, and you run every
mechanical part (config + readiness + realized-stamp resolution, cut validation, the
write-gate — which writes its machine verdict, the keyed in-place persist of the spine
`epics` index, the post-write scoped guard, the stop-condition gate) through bundled
scripts. Under direct-model-write (ADR 026) the author-epics skill writes each `epic.md`
straight to the live model and you NEVER write epic YAML or the spine yourself; the keyed
persist writes the shared `epics` index. Write-then-review (ADR 026): the full delta is
written to the live model FIRST, then guarded and reviewed, and nothing is COMMITTED before
the single checkpoint resolves (C9 — class: standard per gate-config.md; a config-resolved
skip is recorded in evidence, never silent). The play ends by PROVING its Done means (C17),
never by the step list running out — and the grilling rounds are human-paced and UNCAPPED:
their only exit is a clean tension report, never an iteration cap.

**Forbidden:** hand-writing epic YAML or the spine; cutting from a slice not stamped
`realized`; issuing a push-back without a citation to a declared item; **closing a tension
without a typed human response** — marking `resolved`/`accepted` because the agent revised
the cut itself, or filling `pushback`/`human_response`/directive/reason with anything but the
actual conversation (#436); attaching a recommendation, option menu, or advocacy to a
grilling question, or asking questions in a batch instead of one at a time; cutting an
epic whose user check depends on a delivery method no declared item decides and no human
chose; persisting the spine `epics` index by any route other than `scripts/persist_epics.py`;
COMMITTING the model delta before the checkpoint resolves; running against a dirty product-os
tree (C18/F19); closing COMPLETED without the stop condition held
(C17/F18); capping the grilling rounds — the grilling loop is human-paced and uncapped,
the cap concept does not apply to human rounds (C17); editing a lens, the slice record,
an ICE, or the profile (a lens defect
is recorded and routed, never patched); hand-rolling git/issue/PR/merge work — the model
change opens ONLY via the injected `start-change` head and closes ONLY via the injected
end-sequence members, and no implementation issue or branch for an epic's delivery work
is ever opened (delivery's job); performing any epic lifecycle step beyond
creation at `ready`.

**Agent boundaries:**

| Agent | Domain | Skills it invokes | Phases |
|-------|--------|-------------------|--------|
| `product-os-keeper` | Read the slice's hub (functionality ICEs + profile) and all seven lenses; author the epic cut by the user-testability grain (self-contained, referenced, ordered, deferrals recorded), writing each `epic.md` + the `deferrals.yaml` straight to the live model and emitting the spine `epics`-index delta into the manifest (never the spine); revise them in place per grilling-round directives; detect tensions between the cut and the declared design, each with a verbatim citation | `author-epics`, `check-cut-tensions` | Draft, Grill |

`product-os-keeper` is the single **domain agent** this play uses (1 of the ≤5 budget).
No utility agents are needed in the play's own core — git/issue machinery arrives only via
the injected members (`start-change` at the head; `commit-change`, `propose-change`,
`review-change`, `merge-change` at the tail), which carry their own agents; those are not
counted here.

## Pre-flight

| Check | Constraint | Action on Failure |
|-------|-----------|-------------------|
| Resolve config + `product_base` (`.garura/core/config.yaml`) | — | Hard halt |
| Slice argument supplied (`/grill <slice-id>` or `/grill <domain>/<slice-id>`) | C1 | Hard halt — name the slice to cut |
| Profile firmed (`set`) AND the slice exists with every functionality ICE resolved + rich | C1 | Hard halt (REC2) |
| Slice stamped `realized` AND all seven lens files present | C1 | Hard halt (REC2) |
| **Clean model tree** — asserted after Step 0 (start-change) cuts the fresh branch | C18/F19 | Hard halt (REC19) |

**Clean-tree assertion (C18/F19, ADR 026).** `HEAD` is only a correct base for the scoped
guard and the git diff if the product-os tree is clean when the writes begin. /grill's issue
and branch arrive via the injected `start-change` head (Step 0), which cuts a fresh branch
off latest main — so the product-os tree is clean by construction. Assert it explicitly right
after Step 0 and before the first write (Step 1); halt if dirty (REC19):

```
test -z "$(git status --porcelain -- <product_base>product-os)" || { echo "HALT: dirty product-os tree (REC19)"; exit 1; }
```

Resolve config mechanically, then resolve the slice + hub, then assert the realized stamp.
Pre-flight itself runs no git — it resolves config and gates only:

```
python3 scripts/preflight.py --play grill --config .garura/core/config.yaml
python3 scripts/check_ready_slice.py --product-base <product_base> --slice <slice>
```

`preflight.py` returns config facts (`product_base`, `stm_base`, `evidence_record`).
`check_ready_slice.py` is the single gate this play exists behind (C1): it resolves the
slice and its hub and halts unless the profile (in the spine) is `set`, the slice's spine
`status` is `realized`, and all SEVEN lens grounding docs exist for it
(`quality.md`, `ux.md`, `agentic.md`, `marketing.md`, `architecture.md`, `run.md`,
`measure.md`) — /grill cuts solved designs only. The hub resolves each of the slice's
`functionality_ref`s to its `functionality.md` grounding doc; an unresolved ref fails loud.
On success it emits `slice_file`, `domain`, `lens_dir`, and the resolved
`functionality_groundings`. On any halt, route to the missing step (REC2): /shape to shape
the slice, /understand to detail it, or the realize pipes (functional: /ux /agentic
/marketing; non-functional: /arch /quality /run; deliver: /measure, which stamps it
realized) to solve and stamp it. No epic is written on a halt.

The working base is `<working>` = `{stm_base}_grill/<slice-id>/` — the STM, non-model
artifacts: the rounds, the epics manifest (`epics-manifest.yaml`), the write-gate verdict,
the approval marker, the keyed persist record (`persist-manifest.json`), the captured
scoped-guard report (`guard-report.json`), and the status markers. There is no `draft/`
model tree (ADR 026). `<slice_dir>` = `<product_base>/product-os/<domain>/slices/<slice-id>`
(the folder beside the record, holding `lens/`, `decisions/`, and the `epics/` home this
play writes IN PLACE on the live model). The stop condition evaluates against `<working>`.

Right after the resolvers, record the session identity stamp's start marker (#463 —
soft-fail, never a halt):

```
python3 scripts/session_stamp.py --phase start \
    --marker "<working>/status/session-stamp-grill.json" \
    --cwd "$(pwd)" --branch "$(git branch --show-current)"
```

**Resume check:** if `{stm_base}_grill/status/<slice-id>.json` exists, resume — skip
completed steps, reset any in-progress step to pending, continue from the first
incomplete.

## Task DAG

Create ALL tasks immediately after resolving config — before any domain work.
The play owns this DAG; the agent must not edit its top-level tasks.

Write-then-review (ADR 026): the FULL cut — the LLM's `epic.md` docs AND the keyed persist's
spine `epics`-index entries — is written to the live model BEFORE the checkpoint, so the
guard and the human both see the real git diff. Nothing is COMMITTED before the gate resolves;
cancel reverts the uncommitted writes.

```
[T0]  start-change   (injected — start, head)   blockedBy: []
[T1]  Draft the cut (epic.md docs to live)       blockedBy: [T0]
[T2]  Validate the live cut                      blockedBy: [T1]
[T3]  Grill rounds (natural human loop — human-paced, uncapped; exit = clean tension report)  blockedBy: [T2]
[T4]  Persist (keyed, in place — spine epics)     blockedBy: [T3]
[T5]  Guard the full delta                        blockedBy: [T4]
[T6]  Checkpoint (approval over the full git diff) blockedBy: [T5]
[T7]  Commit the model delta (feat(model))        blockedBy: [T6]
[T8]  commit-change   (injected — end #1)         blockedBy: [T7]
[T9]  propose-change  (injected — end #2)         blockedBy: [T8]
[T10] review-change   (injected — end #3)         blockedBy: [T9]
[T11] merge-change    (injected — end #4)         blockedBy: [T10]
[T12] Scenario Validation                         blockedBy: [T11]
[T13] Close                                       blockedBy: [T12]
```

Mark each task in-progress before its step and completed right after its eval passes.
No runtime reordering. On resume, skip completed and reset in-progress to pending.

## 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, dispatched with `parent_run_id` so it
emits only its own C1 evidence and this play's close absorbs it. It resolves or creates
the grill issue, cuts the branch off fresh main, sets up a worktree iff config calls for
it, and initializes the STM workspace. /grill is self-contained: everything after this —
the draft, the grilling, the persist — runs on this branch, and the injected end sequence
(Steps 7–10) closes it. No implementation issue or branch for an epic's delivery work is
opened here — that remains /start's job.

    {
      "play":          "start-change",
      "parent_run_id": "<this run id>",
      "inputs":  { "title": "grill slice <slice-id>" },
      "outputs": { "result": "<working>/start/start-change.json" }
    }

start-change owns its own evals (issue anchored, branch off latest main, worktree per
config, STM initialized); they are not re-checked here.

### Phase: Draft

**Step 1 — Draft the cut (epic.md docs to live)** · Owner: `product-os-keeper` · Depends on: Step 0
The agent reads the slice's hub (its functionalities' ICE — resolved by the readiness
gate — plus the profile) and **all seven lenses** (the solved design — /grill is the
reconciliation point, so unlike a realize lens it reads everything), then invokes
`author-epics` to cut the epics: each by the user-testability grain, self-contained with a
concrete `user_check`, referencing the slice's functionalities, ordered with explicit acyclic
dependencies, with a `deferrals.yaml` recording any slice functionality deliberately not cut
this run. Per ADR 026 the skill writes each `epic.md` and the `deferrals.yaml`
**straight to the live model** under the slice's `epics/` home, and emits the spine
`epics`-index delta as structured data in the manifest (it never writes `_spine.yaml`):

    {
      "task":    "cut this realized slice into user-testable epics — each a meaningful increment a user can open, exercise, and verify when delivered, self-contained (context + acceptance + a concrete one-line user_check), threading the slice functionalities it needs, referencing ICE/lenses never copying them, ordered (acyclic depends_on, first epic stands alone, order 1..n), with every slice functionality covered or explicitly deferred with a reason. Write each epic.md and the deferrals.yaml IN PLACE under the slice's epics/ home on the live model; emit the spine epics-index entries + deferral ids into the epics-manifest — never write _spine.yaml. Declare each epic's surface per surface-contract.md — a surface block {type: web_dashboard|server_api|cli|library|service_read_model, human_run_target, must_open[]} sourced from the slice's own named surface (slice.surface — the authoritative user-facing surface /shape wrote) and refined by the epic's user_check; the epic surface must trace to a surface the slice names, never invented and never silently defaulted to a non-user-facing type. When the slice surface is user-facing (web_dashboard/server_api), the first epic (order 1) must scaffold a minimal openable app/server shell in its acceptance/scope so later epics extend a surface that already opens.",
      "inputs":  { "slice_ref": "<domain>/<slice-id>",
                   "slice_file": "<product_base>/<slice_file>",
                   "functionality_groundings": [ "<product_base>/<functionality.md>", "..." ],
                   "lens_dir": "<product_base>/<lens_dir>",
                   "spine": "<product_base>/product-os/_spine.yaml",
                   "product_base": "<product_base>",
                   "manifest_path": "<working>/epics-manifest.yaml" },
      "outputs": { "epics_manifest": "<working>/epics-manifest.yaml",
                   "epics_home": "<slice_dir>/epics/" }
    }

`slice_file`, `lens_dir`, and `functionality_groundings` come from `check_ready_slice.py`. The
skill reads the hub + the seven lenses **read-only**, writes the `epic.md` docs +
`deferrals.yaml` IN PLACE on the live model, and writes `epics-manifest.yaml` under `<working>`
(STM) with the epics-index delta as structured data. It writes NO shared model file. It
returns the contract with the output paths on disk — never inline content.
**SE-1 (F2/C1):** the readiness + realized gates passed at pre-flight — the profile is
`set`, the slice exists with every functionality ICE resolved + rich, the slice record is
stamped `realized`, and all seven lens files exist; otherwise the run halted (REC2) and no
epic was written.
**SE-20 (F19/C18):** the product-os tree was clean at entry — the assertion right after Step 0
(`git status --porcelain -- <product_base>product-os` empty) passed on the fresh `start-change`
branch; a dirty model tree halted (REC19), so the git diff and the scoped guard reflect only
this run's delta.

### Phase: Validate

**Step 2 — Validate the live cut** · Owner: play · Depends on: Step 1
Run the cut validator over the manifest + the live docs before any grilling. Under
direct-model-write the `epic.md` docs are already on the live model and the epics-index delta
is in the manifest (there is no draft tree):

```
python3 scripts/validate_epics.py --manifest <working>/epics-manifest.yaml \
        --product-base <product_base> --slice-ref <slice_id>
```

**SE-2 (F1/C2):** every epic carries a non-empty, distinct `user_check` and at least one
acceptance criterion — and, by judgment over the validator-clean draft, each acceptance
criterion names observable user behavior (an actor opens/does/verifies something), never
internal-only completion; any epic failing this is regenerated (REC1) before grilling.
**SE-3 (F10/C3):** every epic is self-contained — `context.persona`/`systems`/`scope` and
`acceptance` non-empty, and every `functionality_refs` entry resolves to a functionality
the slice declares; gaps are backfilled from the hub and lenses (REC10).
**SE-16 (F15/C14):** every epic in the draft carries a `surface` block with a `type` from
the surface-contract taxonomy, a `human_run_target`, and — for a user-facing type
(web_dashboard/server_api/cli) — a non-empty `must_open`; no surface is absent or silently
defaulted to a non-user-facing type. A missing or defaulted surface is declared with the
human at the checkpoint (REC15) before the cut is written.
**SE-17 (F16/C15):** when the slice surface is user-facing (web_dashboard/server_api), the
first epic (order 1) scaffolds an openable app/server shell — its acceptance/scope name a
minimal app a user can open — so later epics extend an existing surface; otherwise the cut
is reordered or recut (REC16).
**SE-4 (F4/C4):** no epic embeds source content — the forbidden keys (`ice`, `intent`,
`goals`, `expectations`, `lens`, `content`) appear in no epic; a copy found is replaced
with a reference (REC4).
**SE-5 (F3/C5):** the coverage check maps every slice functionality to ≥1 epic's
`functionality_refs` or a deferral with a recorded reason — never both, never neither; an
unmapped functionality gets an epic or an explicit deferral (REC3).
**SE-8 (F9/C8):** `order` values are unique positive integers, every `depends_on` resolves
to an epic in this cut, the graph is acyclic, and the lowest-order epic has no
dependencies; violations are re-ordered or split (REC9).
**SE-11 (C10):** every epic's `status` is `ready` and `issue_ref` is unset — creation
only; pickup, delivery, and deletion belong to /start and /merge.
On any GAP, apply the named recovery and re-run before grilling starts.
**SE-18 (F17/C16):** every drafted `epic.md` passes `lint_grounding.py` (kind `epic`: the template's section set, no drift) AND the content-quality eval gate (`grounding_gate.py` over the judge verdict); and each epic's spine entry conforms to the spine schema with `functionality_refs` that resolve. A thin or off-template epic doc, or a dangling ref, fails the gate and is not written.

### Phase: Grill (rounds — the play's reason to exist)

**Step 3 — Grill rounds, until clean** · Owner: play + `product-os-keeper` · Depends on: Step 2
A NATURAL human loop (C17) — human-paced and UNCAPPED: an iteration cap is never applied
to human rounds; the cap concept does not apply to grilling. Round `R<n>` runs five moves
and the loop exits **only** when a round's report has zero `live` tensions, zero
unanswered decision questions, and the validator is clean against the rounds — a clean
tension report, however many rounds the human needs:

1. **Tension check.** Dispatch `product-os-keeper` → `check-cut-tensions` over the current
   cut and everything the slice declared:

       {
         "task":    "detect tensions between this epic cut and the declared design — the functionality grounding docs, all seven lenses, profile bars — one entry per real contradiction, each citing source file + verbatim quote; emit unresolved delivery-method choices that shape an epic as cited decision_questions entries (plain questions, no recommendations, human_response always absent); suppress tensions and questions already closed in prior rounds",
         "inputs":  { "epics_home": "<slice_dir>/epics/",
                      "slice_file": "<product_base>/<slice_file>",
                      "functionality_groundings": [ "<product_base>/<functionality.md>", "..." ],
                      "lens_dir": "<product_base>/<lens_dir>",
                      "spine_path": "<product_base>/product-os/_spine.yaml",
                      "round_id": "R<n>",
                      "prior_rounds_dir": "<working>/rounds/" },
         "outputs": { "report": "<working>/rounds/R<n>-tensions.yaml" }
       }

2. **Build the round's question list.** One question per live tension (the push-back —
   steelman the analysis of *what* to challenge, but state the question plainly) and one
   per open decision question. Each list entry is the citation plus one simply-stated
   question. Nothing else: **no recommendation, no option menu, no advocacy** wrapped
   around it (C12).
3. **Ask one at a time.** Present the first question — the verbatim citation and the
   question — and **wait for the typed answer** before asking the next. As each is asked,
   record on its entry `pushback.shown_to_human: true`, `pushback.text` (what was shown),
   and `pushback.asked_at`; when the human answers, record `human_response.text` (their
   words) and `human_response.answered_at`. A question with no citation is never asked
   (C6) — withdraw it (REC5).
4. **Disposition from the answers only (C12).** For each answered tension: `resolved` —
   with a `resolution_directive` derived from the human's words — or `accepted` — with
   the human's own `resolution_reason`. For each answered decision question: record the
   chosen delivery strategy on the entry; it becomes a revision directive where it
   reshapes an epic (C13). The agent revising the draft by itself NEVER closes a tension
   — no answer, no disposition (F11). A tension that exposes a defect in a lens itself is
   dispositioned by recording the defect to `<working>/lens-defects.yaml` and routing the
   human to that lens's play — the lens is never edited here (C11). Revisions go back
   through the agent: dispatch `product-os-keeper` → `author-epics` with `directives` =
   the dispositions + chosen strategies, revising the live `epic.md` docs + the manifest in
   place (never the spine).
5. **Re-validate.** After any revision, re-run Step 2's validator, then enter round
   `R<n+1>`.

The write-gate closes the loop — the validator in rounds form must be clean, and it
writes its machine verdict (`ok`, `counts.live_tensions`,
`counts.decision_questions_open`) to `<working>/write-gate.yaml` — the final tension
report's machine-readable form. The round reports themselves are a closed schema
(C13/F14) and never carry machine fields; the stop condition (C17) reads this verdict
file instead:

```
python3 scripts/validate_epics.py --manifest <working>/epics-manifest.yaml \
        --product-base <product_base> --slice-ref <slice_id> \
        --rounds-dir <working>/rounds \
        --out <working>/write-gate.yaml
```

**SE-6 (F5/C6):** every tension entry across every round report carries a citation
(`cites.source` + verbatim `cites.quote`) — the validator fails on any uncited entry, and
the session shows no push-back that lacks one; an uncited push-back is withdrawn (REC5).
**SE-7 (F6/C7):** at the write-gate, zero tensions are `live` — every one is `resolved`
in the cut or `accepted` with a recorded reason; live tensions block the gate (REC6).
**SE-12 (F11/C12):** every non-live tension carries the human loop's evidence —
`pushback.shown_to_human: true` with the text shown, `human_response.text` in the human's
words, and a `resolution_directive` (resolved) or human-given `resolution_reason`
(accepted); the validator fails any closed tension missing them, and the session shows
each was asked one at a time with no recommendation attached (REC11).
**SE-13 (F12/C13):** every `decision_questions` entry carries its citation, its question,
and the human's answer — the validator fails an unanswered one, and a `resolved` entry
also carries the `resolution_directive` derived from that answer; no epic's `user_check`
rests on a delivery method that neither a lens nor a recorded human answer decides
(REC12).
**SE-15 (F14/C13):** round reports are a CLOSED SCHEMA — the write-gate validates evidence
only under `tensions:` and `decision_questions:`, reads the legacy `questions:` key as an
alias for `decision_questions:` and holds it to the identical rules (counting it), and
fails any other top-level evidence key rather than passing it unchecked. The
decision-question count therefore reflects every round question; an off-schema key — under
which the gate would otherwise validate nothing and report a clean pass — is a forged-clean
gate and blocks the write until the round is rewritten under the canonical schema (REC14).
**SE-19 (F18/C17):** the loop's exit is proven, never assumed — the final write-gate run
wrote its verdict to `<working>/write-gate.yaml` reading `ok: true` with
`counts.live_tensions: 0` and `counts.decision_questions_open: 0`, and the close binds on
the stop-condition verdict (the write-gate verdict + the approval marker + the keyed persist
manifest + the scoped-guard report): `held` permits COMPLETED; anything else closes HALTED
(REC18). The grilling
loop itself is human-paced and UNCAPPED — its only exit is the clean tension report; no
round counter exists and no cap-halt path exists for human rounds.

### Phase: Persist (write the full delta first, ADR 026 write-then-review)

**Step 4 — Persist (keyed, in place — spine epics)** · Owner: play · Depends on: Step 3
Write-then-review (ADR 026): the FULL model delta is written to the live model BEFORE the
checkpoint, so the guard and the human all see the real delta. The `epic.md` docs +
`deferrals.yaml` are already on the live model (Step 1). `persist_epics.py` now writes the
SHARED file — the spine `epics` index — in place, keyed to `--slice-ref`: it reads the
manifest's epics-index delta and merges each entry into the live spine `epics` index
(additive by id, replacing only a `ready` epic the cut re-states, never an `in_delivery`
one), and it REFUSES to touch any epic delivery owns or any spine field outside `epics`
(this is the node-level containment the file-level guard cannot provide). No draft, no doc
copy. It is all-or-none: if anything is refused, nothing is written. Nothing is COMMITTED yet
— the commit (Step 7) happens only after the gate approves; on cancel the whole delta is
reverted (Step 6):

```
python3 scripts/persist_epics.py --manifest <working>/epics-manifest.yaml \
        --product-base <product_base> --slice-ref <domain>/<slice-id> \
        --out-manifest <working>/persist-manifest.json
```

**SE-9 (F7/C9):** the keyed persist manifest (`<working>/persist-manifest.json`) reads
`ok: true` with every approved epic in `written` and `refused` empty — all-or-none; a refused
plan writes nothing (REC7). Nothing is COMMITTED here — the model delta is committed only at
Step 7 after the checkpoint (Step 6) resolves.

### Phase: Guard (over the full delta)

**Step 5 — Guard the full delta** · Owner: play · Depends on: Step 4

**The run's write scope (the per-play guard policy, ADR 026).** The old `apply_epics.py`
encoded /grill's write scope by construction — the slice's `epics/` home (the `epic.md` docs
+ `deferrals.yaml`) and the spine `epics` index, and nothing else. Under direct-model-write
that same scope is the `scoped_write_guard.py` policy — resolve `<slice-epics>` as the slice's
epics home relative to `product-os/` (e.g. `<domain>/slices/<slice-id>/epics`):

    --allow 'product-os/_spine.yaml'                    # the shared epics index (persist writes it)
    --allow 'product-os/<slice-epics>/*'                # the epic docs + deferrals.yaml (add or re-cut)

**Guard ONCE over the full delta (C11).** After ALL writes (the LLM docs from Step 1 and the
keyed persist's spine write from Step 4), run the scoped guard a single time over the whole
delta. Capture its report — its `ok` field is the stop condition's D7 input (this replaces the
old `check_epics.py` before/after verify). /grill writes NO decisions, so the scope carries no
`decisions/*` glob:

```
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
        --allow 'product-os/_spine.yaml' --allow 'product-os/<slice-epics>/*' \
        --out <working>/guard-report.json
```

If the guard exits non-zero, re-run with `--restore` to revert the offending paths, apply
REC8 (a model path outside the slice's epic scope changed), and re-persist before the
checkpoint.

**SE-10 (F8/C11):** the scoped-write guard report reads `ok: true` — the model delta is
confined to the slice's `epic.md` docs, its `deferrals.yaml`, and the spine `epics` index; no
slice record, lens, functionality, profile, or any other spine field changed. Node-level
containment (only `epics` changed; an in-delivery epic untouched) is kept by the keyed persist.

### Phase: Checkpoint (config-gated, C9)

**Step 6 — Human review (class: standard)** · Owner: play · Depends on: Step 5
First record the checkpoint's resolution as the **approval marker** — the machine field the
stop condition reads (C17): after the gate resolves approve, write `<working>/approval.json`
as `{"approved": true, "mode": "typed" | "config-skip", "detail": "<the human's words | the
config resolution path>", "at": "<ts>"}`.

The checkpoint is a config switch of class `standard` per `gate-config.md` — NOT
pinned: when the switch resolves off, the gate records `gate skipped by config
(<resolution path>)` in the evidence's Checkpoint Decisions, writes the approval marker with
`mode: config-skip`, and proceeds — visible, never silent. The switch touches ONLY this
close-side checkpoint; the grilling loop's per-question HITL discipline (C12/C13 — one
question at a time, typed answers, never self-resolved) is the loop's interior, not a close
gate, and no config value touches it.
Write-then-review (ADR 026): the full delta is already on the live model (docs by Step 1, the
spine `epics` index by Step 4), so present the full cut **inline over the real model git
diff**, in product language: each epic — its title, outcome, the `user_check` (what a user
opens, does, and sees), its declared **surface** (the type, what a human runs/opens, and the
artifacts that must open), the acceptance criteria, the functionalities it threads, its
dependencies and order — plus the deferrals and their reasons, **the grilling record** —
every resolved tension (the question asked, the human's answer, the change made), every
accepted tension and the human's reason, every delivery-method decision the human made and
which epics it shaped (C12/C13) — and any lens defects recorded for routing. When the gate
fires (the default), render the approval prompt (`standards/templates/approval-prompt.md`)
and wait for a typed response. Approve → continue to Step 7 (commit). **Cancel → revert the
working tree (ADR 026 step 6):** the full delta is already on disk, so run the guard with
`--restore` and an EMPTY allow set to `git restore` the modified model paths and
`git clean`/remove the new ones (byte-clean back to HEAD), then halt — nothing was committed,
and cancel means "revert what was written":

```
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
        --restore --out <working>/guard-report.json   # empty --allow ⇒ every model path reverted
```

### Phase: Commit (make the delta durable, ADR 026 step 7)

**Step 7 — Commit the model delta** · Owner: play · Depends on: Step 6
The gate approved (or was skipped by config). Commit the full model delta on the branch (C18,
ADR 026 step 7) — a lightweight persist step that makes the writes durable and advances HEAD
so the injected end sequence carries a committed model delta. A cancelled checkpoint never
reaches this step — its tree was already restored in Step 6:

```
git add -- <product_base>product-os
git commit -m "feat(model): grill <slice-id> into epics (#<issue>)"
```

### Phase: End sequence (injected — D2 position: both, tail)

The `feat(model)` commit (Step 7) already made this run's model delta durable; the standard
end sequence raises, reviews, and lands it — the same branch the injected `start-change` head
opened (#437, decision 24). Each member runs as a sub-play dispatched with `parent_run_id`
(emits only its own C1 evidence; this play's close absorbs it). Each member is independent
and resolves its own context from the branch and config; this play passes no hand-rolled
git/PR/merge logic.

**Step 8 — commit-change** · Owner: `commit-change` (sub-play) · Depends on: Step 7 —
commit any work still uncommitted after the `feat(model)` model-delta commit (STM evidence
and the run records), grouped by concern with conventional messages; no push. The epics and
deferrals were already committed as the `feat(model)` delta in Step 7.

**Step 9 — propose-change** · Owner: `propose-change` (sub-play) · Depends on: Step 8 —
run the scope-and-quality self-review, push the branch, open the PR carrying the review.

**Step 10 — review-change** · Owner: `review-change` (sub-play) · Depends on: Step 9 — run
the diff-scoped quality check, post an approve/reject verdict. A reject stops the
sequence before merge.

**Step 11 — merge-change** · Owner: `merge-change` (sub-play) · Depends on: Step 10
(approve verdict) — merge the PR, switch to main and pull, delete the feature branch.

    {
      "play":          "<commit-change | propose-change | review-change | merge-change>",
      "parent_run_id": "<this run id>",
      "inputs":  {},
      "outputs": { "result": "<working>/end/<member>.json" }
    }

**SE-14 (F13):** after the model delta is committed (Step 7), the end sequence ran —
`<working>/end/` holds each member's result in order, starting with `commit-change.json` (the
durable model change did not sit waiting on a manual `/commit-change`); a member's own halt
(e.g. a review reject) stops the chain by that member's rules and is visible in its
result, never silent (REC13).

### Phase: Scenario Validation

**Step 12 — Scenario evals** · Owner: play · Depends on: Step 11
- **SCE-1 (S1 — product builder):** the slice's `epics/` holds the approved cut — every
  epic's acceptance names observable user behavior, the keyed persist manifest shows one
  all-or-none transaction and the approved cut was committed as a single `feat(model)` model
  delta, and no stale `ready` epic from an earlier run survives (the persist replaces them).
- **SCE-2 (S2 — delivery engineer):** the first epic in the order has no `depends_on`;
  every epic carries persona/systems/scope context plus acceptance; every
  `functionality_refs` entry and the `slice_ref` resolve to existing records.
- **SCE-3 (S3 — product builder under grilling):** the round reports show every tension
  cited (source + verbatim quote) and the session issued no push-back without one.
- **SCE-4 (S4 — product builder):** the coverage check maps every slice functionality to
  ≥1 epic or a deferral with a recorded reason — zero unmapped, zero silent drops.
- **SCE-5 (S5 — product builder, too early):** on an unrealized slice the play halted at
  pre-flight naming the missing stamp or lens files, and wrote no epic.
- **SCE-6 (S6 — lens owner):** any lens defect found while grilling is recorded in
  `<working>/lens-defects.yaml` naming the lens and the tension, the human was routed to
  that lens's play, and the lens files + slice record are byte-identical before and after
  the run.
- **SCE-7 (S7 — product builder, grilled for real):** every non-live tension in the round
  reports carries `pushback.shown_to_human: true` with the push-back text, a
  `human_response` with the human's own words, and a `resolution_directive` (resolved) or
  human-given `resolution_reason` (accepted); the write-gate fails when any is missing;
  the session asked its questions one at a time, plainly, with no recommendation attached.
- **SCE-8 (S8 — product builder, open delivery choice):** every `decision_questions`
  entry carries a citation, the question, and the human's answer; the write-gate fails an
  unanswered one; the checkpoint showed the chosen delivery strategy and the epics it
  shaped. The round reports carry question evidence only under the canonical keys (a legacy
  `questions:` list is aliased to `decision_questions:`, counted, and held to the same
  rules); the decision-question count equals the real number of round questions, and no
  off-schema key let the gate report a clean pass over unchecked evidence (F14).

### Phase: Evidence & Close

**Step 13 — Close** · Owner: play · Depends on: Step 12
Run the Standard Play Close. /grill is a **product-scoped** play (no issue) — use the
product-scoped evidence base and slug. Evidence recording is play-only and config-gated
per the D1 evidence rule (`standards/rules/evidence-recording.md`).

```bash
# --- Standard Play Close (canonical; see standards/rules/play-close.md) ---
# Path tokens resolved at pre-flight (resolve here if not already):
#   ltm_project_target  = yq '.ltm.project-target' .garura/core/config.yaml
#   evidence_base, slug:
#     project-scoped play : evidence_base="${stm_base}${issue}/evidence/grill/" ; slug="#${issue}"
#     product-scoped play : evidence_base="${product_base}_evidence/grill/"     ; slug="${product_slug}"
evidence_template=$(cat "${ltm_project_target}standards/templates/evidence-file.md")
delivery_template=$(cat "${ltm_project_target}standards/templates/delivery-report.md")
ts=$(date -u +%Y%m%d-%H%M%S)
evidence_dest="${evidence_base}${ts}.md"
mkdir -p "$(dirname "$evidence_dest")"
# Session identity stamp (#463) — close phase; start phase ran at pre-flight
session_stamp=$(python3 scripts/session_stamp.py --phase close \
    --marker "<working>/status/session-stamp-grill.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Evaluate the Done means against the run's grill working base as the
# close's authoritative input.
python3 scripts/check_stop_condition.py \
    --manifest "<play-dir>/stop-condition.yaml" \
    --base "<working>/" \
    --out "<working>/status/stop-condition-grill.yaml"
sc_exit=$?   # 0 held · 1 unmet · 2 error
```

`/grill` is product-scoped: `evidence_base="${product_base}_evidence/grill/"` and
`slug="${product_slug}"` (the slice, e.g. `ai-usage-intelligence/slice-token-data-spine`).

**Step C0 — bind the verdict.** `sc_exit == 0` (held) permits `status: COMPLETED` — the
write-gate verdict clean (zero live tensions, zero open decision questions), the
checkpoint resolved in the approval marker, the keyed persist manifest ok, and the
scoped-write guard report ok. 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.
There is no `loop_cap_exhausted` here — the grilling rounds are human-paced and
uncapped; an unconverged grilling simply holds the run open (HALTED at close, resumed
at the open tension or unanswered question).

**Step C1 — Write evidence file.** Gated by the resolved `evidence.record` flag (global +
per-play `evidence.plays.grill`; first match wins, 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 `grill`, run_id `grill-${ts}`, product_slug = the
slice, started_at/completed_at, status per C0, exit_reason; artifacts produced: the epics,
the deferrals, the round reports with dispositions, the write-gate verdict, the approval
marker, any lens-defect records, the keyed persist manifest (`persist-manifest.json`), the
captured scoped-guard report (`guard-report.json`), the `feat(model)` model-delta commit sha,
the member results (start-change + the end sequence), the stop-condition verdict; step and
scenario eval results SE-1…SE-20 / SCE-1…SCE-8; checkpoint decision from Step 6 —
including a `gate skipped by config (<resolution path>)` row when the standard-class
switch resolved off; the session identity stamp fields from $session_stamp (#463):
session_id, ledger_file, ledger_start_offset, ledger_end_offset (null when unresolved —
never blocks the close); and stop_condition per C0 with the Stop Condition section
filled) and write to `$evidence_dest`. Do NOT hand-author the body.

**Step C2 — Render delivery report.** Also render the **Next** line: resolve this play in `standards/rules/pipeline-next.md` and emit `**Next:** /<command> — <why>. Or run /next to see all recommended actions.` (only /next pointer, or omit, when the mapped command is null), per `play-close.md`. Fill the `delivery-report.md` slots and output the
report: `## grill Delivered — ${product_slug}`, the Run Summary table, the Pipeline Steps
table from the task DAG, the Artifacts Produced table (the epics with their user_checks
and order, the deferrals, the accepted tensions, any lens defects routed), Next Steps (the cut is merged by the end sequence — run /start on the first epic in the
order to begin delivery; if a lens defect was recorded, run that lens play first), and a pointer to `$evidence_dest`. Always emitted; never gated.

```bash
# --- end Standard Play Close ---
```

## Scenario Validation

| Scenario | Persona | Eval |
|----------|---------|------|
| S1 — the approved cut | product builder | SCE-1 |
| S2 — self-contained pickup | delivery engineer | SCE-2 |
| S3 — cited grilling | product builder under grilling | SCE-3 |
| S4 — explicit deferral | product builder | SCE-4 |
| S5 — too early | product builder | SCE-5 |
| S6 — lens defect routed | lens owner | SCE-6 |
| S7 — grilled for real | product builder | SCE-7 |
| S8 — open delivery choice | product builder | SCE-8 |

## Recovery

| For | Trigger | Direction | Handoff |
|-----|---------|-----------|---------|
| F1 | an epic's acceptance reads as internal completion — no user-observable behavior | regenerate its acceptance from what a user can open and do, merging it with adjacent work until it's testable | autonomous |
| F2 | the realized stamp or lens files are missing | halt; run the missing realize lens plays (quality → ux → agentic → arch → measure → run) first | human |
| F3 | the coverage check finds a slice functionality mapped nowhere | cut an epic for it or record an explicit deferral with reason, then re-present the cut | autonomous |
| F4 | an epic embeds intent or lens content instead of referencing it | replace the copy with a reference to the source record | autonomous |
| F5 | a push-back in the session record carries no citation | withdraw it; re-issue only if a declared item supports it, otherwise drop it | autonomous |
| F6 | live unresolved tensions exist when the write is attempted | block the write; resolve each tension in the cut or record an explicit acceptance, then re-present | autonomous |
| F7 | the model delta was committed with no recorded approval, or the keyed persist wrote only part of the set | revert the working tree (scoped_write_guard.py --restore, empty allow), return to the checkpoint for a fresh approval, re-run the all-or-none keyed persist | human |
| F8 | the scoped-write guard finds a changed model path outside the slice's epic docs, deferrals, and the spine epics index | the guard's --restore reverts the offending paths; re-run confining the write to epics | autonomous |
| F9 | a dependency cycle, or a first epic that can't stand alone | re-order or split epics until the order is acyclic and the first is independently deliverable | autonomous |
| F10 | the self-containment check finds an epic with empty or unresolvable context or acceptance | backfill from the slice's intent records and lenses, re-verify resolution | autonomous |
| F11 | a non-live tension lacks human-response evidence — no push-back shown, no typed answer, or no human-derived directive/reason | reopen the tension to `live`, ask the question plainly (one at a time, no recommendation), capture the typed answer, and disposition from that answer only | human |
| F12 | an epic is shaped by a delivery method the human never chose — a missing or unanswered decision question at the write-gate | ask the cited decision question, capture the choice, revise the cut to match it, and record the decision in the round | human |
| F13 | the persist is verified but no end-sequence member has run — the model change is uncommitted with the play heading to close | enter the injected end sequence (commit → propose → review → merge) before the close; a member's own halt (e.g. a review reject) stops the chain by its own rules, never silently | autonomous |
| F14 | a round report carries question evidence under an unrecognized top-level key (a legacy `questions:` list, or any other off-schema key) | read a legacy `questions:` key as `decision_questions:` and validate it identically; for any other off-schema key, block the write-gate until the round is rewritten under the canonical `tensions:`/`decision_questions:` schema, then re-run the gate | autonomous |
| F15 | an epic at the write-gate has no `surface` block, or a surface defaulted to a non-user-facing type with no human confirmation | source the surface from the slice's own named surface (slice.surface) refined by the epic's user_check per surface-contract.md, put it to the human at the checkpoint, and write it; block the cut until every epic carries a confirmed surface that traces to a slice surface | human |
| F16 | a user-facing slice's first epic scaffolds no app/server shell | rework the order so the first epic seeds a minimal openable shell, or recut so one does, then re-present | autonomous |
| F17 | an epic doc fails the linter or the content eval, or its spine entry fails the schema or names an unknown functionality | re-emit the `epic.md` to its template, rewrite to the judge's cited fixes and re-judge until the gate passes, and fix the spine entry to resolve; never write a degraded epic | autonomous |
| F18 | the run is about to close COMPLETED with the stop-condition verdict missing, unmet, or unevaluable | close HALTED (`stop_condition_unmet`) naming the unmet clauses; resume at whatever is open — the live tension, the unanswered question, the unresolved checkpoint, the unpersisted cut, or the uncaptured guard report — then re-run the write-gate, the keyed persist, the guard, and the stop-condition check; no `loop_cap_exhausted` exists here — the grilling rounds are human-paced and uncapped, the run stays open until the human's answers converge it | autonomous |
| F19 | the product-os tree is dirty at entry (uncommitted model edits present) | halt and ask for a clean model tree — commit or revert the pending model edits (or let the injected start-change head cut a fresh branch off main) — before /grill proceeds | human |

## Pause and Resume

Steps run top to bottom. On entry, resolve config, run the readiness + realized gates,
resolve the target slice from the play argument or the in-progress draft, check the status
marker at `{stm_base}_grill/status/<slice-id>.json`, skip completed steps, reset any
in-progress step to pending, and continue. The grilling rounds resume from the round
reports on disk — closed dispositions (`resolved`/`accepted`) are preserved; a round left
mid-flight re-runs its tension check. Under direct-model-write (ADR 026) there is no draft
tree and no pre-apply snapshot: containment is the post-write scoped guard diffing the model
tree against HEAD. The clean-tree assertion (F19) is scoped to a FRESH start (the fresh
`start-change` branch) — a resume that already wrote `epic.md` docs re-enters its own
in-progress delta, which is expected, not a dirty-tree halt. A fresh start with no marker
runs everything and creates the
marker at Step 1. A close that halted on an unmet stop condition (C17) resumes at whatever
the verdict names open — the live tension, the unanswered decision question, the
unresolved checkpoint, or the unpersisted cut; the grilling rounds are human-paced and
uncapped, so there is never a round cap to exhaust — the run simply stays open until the
human's answers converge it.

## Compilation Metadata

| Field | Value |
|-------|-------|
| fingerprint | sha256:d566ecc331c24164ea2ddcdbfb0ef3066d94b943f8f6df333b6450f64e65d6fa (of `reference/ice.md`) |
| compiled_by | play-editor (#500 direct-model-write, ADR 026); prior: play-editor (#466 Batch C — gated close per ADR 025: Done means baked, stop-condition gate at Step C0, session stamp, checkpoint class standard, uncapped human loop stated), play-creator (#434; edited via play-editor, #436, #437), play-editor (#434, decision 24; closed-schema round-report fix — F14); epic-lifecycle prose corrected #439 (see deviation note) |

**Direct-edit deviation note (#439, ADR 019):** the epic-lifecycle sentence ("epics are temporary… deleted on merge") was corrected to "epics are permanent… stamped `delivered` on merge and kept" in both `reference/ice.md` and this compiled body, and the fingerprint recomputed to match. This is descriptive context about the epic's downstream life, not grill's own guarantee — no constraint, failure condition, scenario, step, or eval changed; grill still cuts epics and deletes nothing. No re-interview/rebuild required.
| pipeline_position | both (start-change head; commit-change → propose-change → review-change → merge-change tail — self-contained) |
| workflow_structure | A (approval checkpoint — class: standard, config-gated per gate-config.md, not pinned; direct-model-write WRITE-THEN-REVIEW per ADR 026 — persist + guard before the gate, feat(model) commit after; grilling rounds as a natural human loop in preparation) |
| stop_condition | stop-condition.yaml (D1–D7), gate live at Step C0 |
| loop | grilling rounds: human-paced, uncapped — exit = a clean tension report (zero live tensions, zero open decision questions) proven by the write-gate verdict; never an iteration cap |
| domain_agents | 1 (product-os-keeper) |
| utility_agents | 0 |
| skills_used | author-epics, check-cut-tensions |
| scripts | 9 (preflight.py, check_ready_slice.py, lint_grounding.py, grounding_gate.py, validate_epics.py — writes the write-gate verdict, persist_epics.py — keyed in-place persist of the spine epics index, scoped_write_guard.py — post-write containment guard, check_stop_condition.py — Done-means gate, session_stamp.py — #463 identity stamp) |
| member_subplays | start-change (injected head); commit-change, propose-change, review-change, merge-change (injected end sequence) |
| step_evals | 20 (SE-1…SE-20; one per failure condition F1–F19, plus SE-11 for C10) |
| scenario_evals | 8 (SCE-1…SCE-8) |
| recovery_entries | 19 (one per failure condition; 13 autonomous / 6 human) |

**Recompiled note (#500, direct-model-write / ADR 026):** migrated from draft-then-apply to
direct-model-write. The old draft model tree is removed; the author-epics skill writes each
`epic.md` (and the slice's `deferrals.yaml`) straight to the live model and emits the spine
`epics`-index delta as structured manifest data; the renamed keyed `persist_epics.py`
(was `apply_epics.py`) writes the spine `epics` index in place; containment is the post-write
`scoped_write_guard.py` (its `guard-report.json` is D7); the old `check_epics.py` before/after
verify was removed (the guard replaces it); the pre-apply file snapshot is gone (the guard
diffs vs HEAD); the play asserts a clean product-os tree at entry (F19, guaranteed by the
fresh `start-change` branch) and commits its own `feat(model)` delta after approval (C18).
Order is **write-then-review** (ADR 026 "Order of operations"): the full cut — the LLM's docs
AND the keyed persist's `epics`-index entries — is written FIRST (Steps 1+4), then guarded
ONCE over the full delta (Step 5), then the gate resolves over the real git diff (Step 6), and
only an approved gate COMMITS (Step 7); the injected end sequence then raises/reviews/lands the
committed delta. Checkpoint cancel reverts the working tree via the guard `--restore`
(empty allow). Grill's checkpoint stays a class-`standard` config switch — NOT a #467
conditional learned gate — so no `classify_change.py` is bundled and no change-shape is
classified. See `standards/rules/direct-model-write.md`.

**Direct-edit deviation note (#500) — INTENT CHANGE, HAND-COMPILED, CONVERGENCE UNVERIFIED:**
This SKILL was updated to the direct-model-write write-then-review shape (ADR 026) by a
**hand-compile from `reference/ice.md`**, NOT by a `/play-editor` run. This is an intent change
(it alters the write path, the containment guarantee, the checkpoint cancel semantics, and the
step order), so the sanctioned path is recompile-via-`/play-editor`; play-editor is
interactive-only (fully gated, human-checkpoint) and cannot run headless in this environment,
so the compiled output was produced by hand to match what play-editor would emit from the
current `reference/ice.md` (fingerprint above). **The `compiled_by` line names play-editor for
provenance intent, but no play-editor run actually occurred and convergence is UNVERIFIED.** An
interactive `/play-editor` convergence run against `reference/ice.md` — confirming the emitted
SKILL matches this hand-compiled body and refreshing the fingerprint — is recommended before
relying on this play in production. `lint_play.py` was run to 0 gaps against this body.

**Direct-edit deviation note (grill-spine-reconcile):** the recompile left the pre-flight,
apply, and verify prose on the OLD model — it called `check_realized.py` (redundant; the gate
is in `check_ready_slice.py`) and the `functionality_ices`/`ice_ref`/`profile.yaml` field
shapes, and the apply/verify commands used the old `--slice-dir`/`--profile-*` CLIs. These were
aligned to the actual reworked scripts (the spine hub-resolution emitting
`functionality_groundings`; `apply_epics.py --draft --product-base` merging the spine `epics`
index; `check_epics.py --manifest --spine-before/after --slice-ref` doing the before/after spine
diff) and `check_realized.py` was deleted. Execution-mechanism only — no constraint, failure,
scenario, or eval changed; the ICE (`reference/ice.md`) and the fingerprint are unchanged.

**Direct-edit deviation note (#466):** SKILL invocation flag corrected to match
`validate_epics.py`'s real interface — the two Step 2/write-gate invocations said
`--slice-file <slice_file>` but the script (like `check_epics.py`) accepts `--slice-ref`
and matches the bare spine slice id, so they now read `--slice-ref <slice_id>`.
Surface-prose only; no constraint, failure, scenario, or eval changed.

Files in this skill

  • SKILL.md59.8 KB
  • reference/ice.md28.6 KB
  • scripts/check_ready_slice.py6.1 KB
  • scripts/check_stop_condition.py5 KB
  • scripts/grounding_gate.py3 KB
  • scripts/lint_grounding.py17.2 KB
  • scripts/persist_epics.py5.5 KB
  • scripts/preflight.py6.4 KB
  • scripts/scoped_write_guard.py9.2 KB
  • scripts/session_stamp.py4.9 KB
  • scripts/test_validate_epics.py6.3 KB
  • scripts/validate_epics.py7 KB
  • stop-condition.yaml1.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.