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
  • 4 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…