Skip to content
Back to skills

Ux

ASecurity

Write a SLICE''s UX lens as a grounding doc (ux.md) — the screens (each naming the object the user works with there, with a low-fidelity layout) that make the slice''s functionalities visible, the flows (per persona and goal: entry, ordered steps naming screens, forks, failure path, exit), the states each screen holds (each with its trigger), and the product''s visual core (the semantic state vocabulary, its tone map, and the design-system reference the look comes from — never a palette or ty...

  • 3 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 10, 2026
designpythonrustgobashnodeawsgit

Security analysis

A100/100

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

Scanned September 22, 2026

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

Installs into .claude/skills of the current project.

Are you the author of Ux?

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

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

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: ux
position: start
description: 'Write a SLICE''s UX lens as a grounding doc (ux.md) — the screens (each naming the object the user works with there, with a low-fidelity layout) that make the slice''s functionalities visible, the flows (per persona and goal: entry, ordered steps naming screens, forks, failure path, exit), the states each screen holds (each with its trigger), and the product''s visual core (the semantic state vocabulary, its tone map, and the design-system reference the look comes from — never a palette or type scale this play picks) — directly in place on the live model. It also DRAWS the slice: a machine-readable wireframe record (lens/screens.yaml) the play authors and a wireframes.html a bundled script renders from it, ordered by journey step, so a product owner can look at the design rather than read a description of it. The START of the FUNCTIONAL realize pipe (ux → agentic → marketing), run on a shaped slice. Accessibility is not here (it lives in the marketing lens); the wider cross-product journey is not here either (that is /story''s). Reads the hub from the spine (functionality grounding + profile + the slice''s persona and journey records — each flow EXPANDS a journey, never invents one), never another lens. Writes only the slice''s ux lens, its wireframe artifacts, and its decisions — the visual core plus any design directive the design settled for /arch and /implement to inherit.'
user-invocable: true
---

# ux

Write a shaped slice's **UX lens** as the grounding doc `ux.md`: the screens that make every
functionality visible (each naming the one object the user works with there), the flows a person
takes through those screens, the states each screen holds, and the product's visual core. It also DRAWS
the slice as wireframes — a record the play authors and a page a script renders from it, ordered by
journey step — because a prose layout is not something a product owner can look at and approve, and
that approval is the whole point of the checkpoint. Just enough to
anchor the intended experience and let the build figure the rest. /ux reads the slice's **hub**
— its functionalities' grounding docs plus the profile (both from the spine) — and never another
realize lens.

**Pipeline position: start.** /ux OPENS the functional realize pipe (ux → agentic → marketing):
the D2 rule prepends `start-change` — resolve or create the slice-realize issue, cut the branch
off fresh main, optional worktree, init STM — so /agentic and /marketing run on this
already-started branch. No pipeline close sequence (no end PR) is injected here; the functional
pipe closes at /marketing. It writes the persistent product model **directly, in place** (the
slice's ux lens, its wireframe artifacts, and its decisions) on the started branch — there is no draft copy and
no apply/promote step; review is the branch git diff and the pipeline's end PR. (#437, #500, ADR 026)

**Write discipline (ADR 026, `standards/rules/direct-model-write.md`).** The LLM authoring skill
writes ONLY the per-node files — the grounding doc `ux.md` and the wireframe record
`lens/screens.yaml` — straight to the live model, and the bundled render script writes only
`lens/wireframes.html` from that record; the one shared-file
mutation — the `decisions/*.yaml` for the visual core and any design directive (slice-level, skip-if-exists) — is done by the
deterministic keyed persist script, in place, keyed to the target slice so it cannot touch another
slice's decisions and never edits an accepted decision in place. The model tree is asserted clean
once start-change has cut the branch (F14) and the play commits its own model delta after approval
(C13), so the working-tree diff vs HEAD is exactly this run's delta. Containment is a post-write
scoped guard (`scoped_write_guard.py`), not a draft.

## Compiled From

This play was compiled from the ux ICE (`reference/ice.md`) by play-editor (#466 Batch C; #467
Batch B — the checkpoint upgraded to a conditional learned gate, see
`standards/rules/gate-config.md`; #500 — migrated to direct-model-write per ADR 026 and
`standards/rules/direct-model-write.md`; #552 — wireframes drawn, visual core narrowed to meaning
plus a design-system reference, decisions widened to design directives, the checkpoint made to hand
over the artifact, and the content eval made to check truth as well as form). Intent defines
constraints (C1–C15) and failure
conditions (F1–F16); the expectation defines success scenarios (S1–S8), a Done means (D1–D4, 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 and step order. You delegate the domain work —
authoring the UX lens grounding doc — to the `product-os-keeper` agent via a JSON contract over
files on disk, and you run the mechanical work (readiness/hub resolution, the shape linter, the
content-quality eval, grounding + coverage, KB grounding, the keyed in-place persist, and the
post-write scoped guard) through bundled scripts and an isolated judge. You never write the lens
yourself, and you never persist the shared decision or commit the model delta before the single
checkpoint (C11) resolves — a typed approval, a recorded config skip, or a recorded policy
auto-pass.

**Forbidden:** hand-writing the lens, a decision, or the wireframe page; writing anything other than this slice's
`ux.md` and `lens/screens.yaml` (by the skill), `lens/wireframes.html` (by the render script), and its
decisions (by the keyed persist) (C2); writing a product-wide surface map, which spans slices (C2);
reading or
grounding on another realize lens (C7); writing a decision by any route other than
`scripts/persist_ux.py`; writing or hand-editing `wireframes.html` by any route other than
`scripts/render_wireframes.py` (F16); choosing a palette or a type scale instead of naming the
design-system reference (C10/F10); taking a checkpoint answer without first handing over the
artifact paths (C11/F11); committing the model delta before the Step 5 gate resolves; editing an
accepted decision in place (additive-only — F9); running the lens writes against a dirty
product-os tree (C13/F14); closing COMPLETED without the stop-condition verdict held (C12/F12).

**Agent boundaries:**

| Agent | Domain | Skill it invokes | Phases |
|-------|--------|------------------|--------|
| `product-os-keeper` | Author the slice's UX lens (screens + flows + states + visual core) AND its wireframe record from the hub + KB pattern grounding — the `ux.md` and `lens/screens.yaml` written in place on the live model, and the visual-core + design-directive decisions emitted into the manifest | `kb-search`, `author-ux-lens` | Author |

`product-os-keeper` is the single **domain agent** this play uses (1 of the ≤5 budget). The
utility work — cutting the branch and resolving the issue — is `start-change` (injected head), not
a domain agent. The content-quality judge always runs as an isolated, clean-context sub-agent
(optionally on a configured different model) — never the orchestrator's own context.

## Pre-flight

| Check | Constraint | Action on Failure |
|-------|-----------|-------------------|
| Resolve config + `product_base` (`.garura/core/config.yaml`) | — | Hard halt |
| Resolve `grounding-eval.judge` (optional model override) | C4 | Default: sub-agent on the session model |
| Slice ready + hub resolves — functionalities, profile, AND the slice's personas + journeys (`check_ready_slice.py`) | C1 | Hard halt (REC1) |
| **Clean model tree** — after start-change (Step 0), `git status --porcelain -- <product_base>product-os` is empty | C13/F14 | Hard halt (REC14) |

Resolve the pre-flight facts mechanically with the bundled resolver:

```
python3 scripts/preflight.py --play ux --config .garura/core/config.yaml
```

Then resolve the slice and its hub from the spine — the readiness gate that every realize lens
shares:

```
python3 scripts/check_ready_slice.py --product-base <product_base> --slice <slice-id> \
    > <working>/readiness.json
```

Keep `readiness.json` on disk — the widened hub (#550) lives in it. Besides the functionality
groundings it carries `personas[]` (`{id, path, name}` for every persona the slice's surfaces and
journeys name) and `journeys[]` (`{id, path, persona_ref, surface_refs, step_count}` for every
journey whose `surface_refs` touch a slice surface). Step 1 reads the records from those paths and
Step 2 hands the file to `validate_ux.py --readiness` as the list the flow ids must resolve
against. An unresolvable persona or a surface reached by no journey is a BROKEN hub — the script
exits 1 and the run hard-halts (REC1); it is never treated as an empty hub.

It asserts the profile is `set` (from the spine), resolves the slice record, and resolves every
`functionality_ref` through the spine to its `functionality.md` grounding doc — the hub. If the
slice is absent, a functionality does not resolve, or the profile is not firmed, hard halt
(C1/REC1).

The run's working root (`<working>` below) is `{stm_base}_realize/ux/<slice>/` — the routing, the
ux manifest (`ux-manifest.yaml`), the change-shape, the persist record (`persist-manifest.json`),
and the captured scoped-guard report (`guard-report.json`) all live under it. These are STM,
non-model artifacts (ADR 008/017) — the model itself (the `ux.md` and the visual-core decision) is
written IN PLACE under `<product_base>product-os/`, never into `<working>`. The stop condition
evaluates against `<working>`. Status markers live at `{stm_base}_realize/ux/status/`.

**Clean-tree assertion (C13/F14, ADR 026).** start-change cuts the branch off fresh main, so the
product-os tree is clean by construction; still assert it right after Step 0 — `HEAD` is only a
correct base for the scoped guard and the change-shape if the tree is clean before the lens writes:

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

If dirty, halt and ask for a clean model tree (commit or revert the pending model edits) before
/ux writes the lens.

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

```
python3 scripts/session_stamp.py --phase start \
    --marker "{stm_base}_realize/ux/status/session-stamp-ux.json" \
    --cwd "$(pwd)" --branch "$(git branch --show-current)"
```

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

## 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 model delta — the LLM's `ux.md` and `lens/screens.yaml`, the
render script's `lens/wireframes.html`, AND the keyed persist's decisions — is written to the live
model BEFORE the checkpoint, so the guard, the
change-shape, and the human all see the real delta. Nothing is COMMITTED before the gate resolves;
cancel reverts the uncommitted writes.

```
[T0] start-change (injected — start, head)          blockedBy: []
[T1] Author the lens + wireframe record (to live)   blockedBy: [T0]
[T1b] Draw the slice (render the wireframes)        blockedBy: [T1]
[T2] Validate the live doc                          blockedBy: [T1b]
[T3] Persist (keyed, in place — decision)           blockedBy: [T2]
[T4] Guard the full delta + classify the shape      blockedBy: [T3]
[T5] Checkpoint (approval over the full git diff)   blockedBy: [T4]
[T6] Commit the model delta                         blockedBy: [T5]
[T7] Scenario Validation                            blockedBy: [T6]
[T8] Close                                          blockedBy: [T7]
```

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: start)

**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 slice-realize
issue, cuts the branch off fresh main, sets up a worktree iff config calls for it, and initializes
STM. /agentic and /marketing run on this branch; /marketing closes it.

    {
      "play":          "start-change",
      "parent_run_id": "<this run id>",
      "inputs":  { "title": "<realize ux: the slice>" },
      "outputs": { "result": "{stm_base}_realize/ux/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. Immediately after it returns, run the clean-tree
assertion (pre-flight, C13/F14) so `HEAD` is a correct base for the guard and the change-shape.
**SE-13 (F14/C13):** the product-os tree was clean once start-change had cut the branch — the
assertion (`git status --porcelain -- <product_base>product-os` empty) passed before any lens
write; a dirty model tree halts (REC14), so the change-shape and the scoped guard reflect only
this run's delta.

### Phase: Author (write the doc to the live model, ADR 026)

**Step 1 — Author the lens (ux.md to live)** · Owner: `product-os-keeper` · Depends on: Step 0
The agent invokes `author-ux-lens` to write the slice's `ux.md` (screens + flows + states +
visual core, per the UX lens template) from the hub (the functionality grounding docs, the profile,
and the slice's persona + journey records — each flow EXPANDS one journey) and KB
pattern grounding. Per ADR 026 the skill writes the per-node doc **straight to the live model** in
place, and emits the visual-core decision (with the grounding map) as structured data in
`ux-manifest.yaml` — it writes NO shared model file (`_spine.yaml`, the profile, or a
`decisions/*.yaml`):

    {
      "task":    "author the slice's UX lens (screens/flows/states/visual core) AND its wireframe record from its hub; write ux.md and lens/screens.yaml in place on the live model — never wireframes.html, which a script renders from your record; ground screens to functionalities and EXPAND each of the slice's journey records into one flow — the journey supplies the persona and the ordered steps, you add the screen each step happens on plus the forks, failure path and exit; never invent a flow; name each flow's journey_ref and persona_ref in the manifest so both resolve against readiness.json; in screens.yaml tie every frame to its journey step through journeys[].steps[].covered_by (never a field on the screen), give every moment a literal frame body, and give any journey step no moment covers a named gap rather than dropping it; fix the visual core as MEANING — the semantic vocabulary, its tone map, the word-alongside-colour rule, and the design-system reference (or an explicit none-yet) — never a palette or a type scale; every flow step names a real screen and every screen is reached by a flow; emit the visual-core decision delta AND a design-directive decision for each non-visual thing the design settled that /arch or /implement must inherit, into the manifest",
      "inputs":  { "slice_ref": "<domain>/<slice>",
                   "slice_file": "<slice record>",
                   "functionality_groundings": "<from check_ready_slice>",
                   "readiness": "<working>/readiness.json",
                   "personas": "<readiness.personas[] — the slice's persona records>",
                   "journeys": "<readiness.journeys[] — the slice's journey records>",
                   "profile": "<spine profile>", "product_base": "<product_base>",
                   "lens_rel": "product-os/<domain>/slices/<slice>/lens/ux.md",
                   "screens_rel": "product-os/<domain>/slices/<slice>/lens/screens.yaml",
                   "manifest_path": "<working>/ux-manifest.yaml" },
      "outputs": { "manifest": "<working>/ux-manifest.yaml" }
    }

The skill reads the hub read-only and writes the `ux.md` and `lens/screens.yaml` IN PLACE under
`<product_base>product-os/`. It never writes `lens/wireframes.html` — that page is rendered from
`screens.yaml` by the bundled script at Step 1b, and a hand-written page is F16.
It writes `ux-manifest.yaml` under `<working>` (STM) with the grounding map and the
`decision_delta` — the visual core (omitted when it reuses an existing product decision) and one
entry per design directive the design settled, each carrying `kind` and the `binds` naming what
downstream reads it. It writes NO shared model
file. It returns the contract with the output paths on disk — never inline content.
**SE-1 (F1/C1):** `check_ready_slice.py` passed at pre-flight — the slice is ready and its hub
resolves, including every surface's persona record and at least one journey per surface; an
unready slice, a dangling `persona_ref`, or a surface no journey reaches halted (REC1).

**Step 1b — Draw the slice (render the wireframes)** · Owner: play (script) · Depends on: Step 1
The record is judgment and the page is not, so the page is generated and never hand-written (C15).
Run the bundled render script over the `screens.yaml` the skill just authored. It validates the
record against C15's shape, writes `lens/wireframes.html` beside the lens, and captures a report
whose `ok` field is D4's input:

```
python3 scripts/render_wireframes.py \
        --screens <product_base>/product-os/<domain>/slices/<slice>/lens/screens.yaml \
        --out <product_base>/product-os/<domain>/slices/<slice>/lens/wireframes.html \
        --report <working>/wireframe-report.json
```

Then prove the page on disk IS the rendered page — the check that catches a hand-edit, now or on a
later run — by re-rendering in memory and diffing, writing nothing:

```
python3 scripts/render_wireframes.py \
        --screens <product_base>/product-os/<domain>/slices/<slice>/lens/screens.yaml \
        --out <product_base>/product-os/<domain>/slices/<slice>/lens/wireframes.html \
        --report <working>/wireframe-report.json --check
```

**SE-15 (F16/C15):** `render_wireframes.py` exits 0 on both passes — `lens/screens.yaml` and
`lens/wireframes.html` both exist; every screen is named by a journey step's `covered_by`; every
journey step either resolves to the moments covering it or carries a named gap (a gap is recorded,
never a halt); every moment holds a frame body; every state's tone is in the visual core's map;
`spec.design_system` names a reference or records `none_yet`; no frame carries an invented palette,
type scale, or component library; and `--check` reports no byte difference, so the page was
rendered and not hand-edited. A finding is REC16: fix the record, never the page, and re-render.

**Step 2 — Validate the live doc** · Owner: play · Depends on: Step 1b
Run the guards over the LIVE `ux.md` this run wrote, before the checkpoint — shape first, then
content, then grounding. Under direct-model-write the doc is already written in place, and the
visual-core decision has NOT yet been written for this run (the keyed persist runs after Step 2):

```
python3 scripts/lint_grounding.py --doc <product_base>/product-os/<domain>/slices/<slice>/lens/ux.md
python3 scripts/validate_ux.py --manifest <working>/ux-manifest.yaml --slice-file <product_base>/<slice_file> --product-base <product_base> --lens <product_base>/product-os/<domain>/slices/<slice>/lens/ux.md --readiness <working>/readiness.json --screens <product_base>/product-os/<domain>/slices/<slice>/lens/screens.yaml
python3 scripts/check_kb_grounding.py --manifest <working>/ux-manifest.yaml --kb-root <kb_root> --proposals-dir <working>/proposals
```

Then run the **content-quality eval** over `ux.md`: spawn an isolated, clean-context sub-agent
handed the judge prompt (`standards/rules/grounding-eval.md`), the doc (at its live path), and the
UX lens per-section guidance (it sees neither the brief nor the author's reasoning), on the model
from `grounding-eval.judge.model`. Gate the verdict:

```
python3 scripts/grounding_gate.py --verdict <verdict.json>
```

**SE-2 (F3/C3):** `lint_grounding.py` exits 0 — `ux.md` conforms to the UX lens template
(Intent/Screens/Flows/States/Visual core), no missing/extra/empty section.
**SE-3 (F4/C4):** the content-quality eval gate (`grounding_gate.py`) passes — `ux.md` is
self-explaining and clears the stranger test — AND `validate_ux.py`'s grounding cross-checks pass,
so the doc is TRUE about the hub and not merely well-formed: every role it names resolves to a
persona record the readiness check handed over (an invented role such as "a general viewer" or
"the actor" is a hard fail, not a wording caveat), and every screen it declares is named by at
least one journey step in `screens.yaml`, with the lens and the record describing the same slice.
A well-formed doc describing people or screens the model does not have FAILS this eval. REC4.
**SE-4 (F5/C5):** `validate_ux.py` — every screen in the manifest grounds to a functionality or a
persona/journey; the visual core grounds to the KB or a decision.
**SE-5 (F6/C6):** `validate_ux.py` — every functionality the slice bundles is visualized by at
least one screen (coverage), and every screen is reached by at least one flow step (no
unreachable screen).
**SE-6 (F7/C7):** `validate_ux.py` — no screen grounds on another realize lens.
**SE-7 (F8/C8):** `validate_ux.py` — the visual core names a decision (the manifest's
`decision_delta` or a reused product decision) that resolves; every design directive the design
settled is in the delta as its own decision naming what it `binds` downstream, rather than left in
the prose; the keyed persist (Step 3) writes them.
**SE-8 (F10/C10):** `check_kb_grounding.py` exits 0 — the navigation and responsive
choices trace to a KB learning or a recorded proposal; and the visual core names the project's
design-system reference or records that none exists yet, with no palette or type scale chosen by
this play (the look is never a KB gap — no shelf can tell a project what its brand is).
**SE-14 (F15/C14):** `validate_ux.py --lens --readiness` reports BOTH flow cross-checks ok. Lens
side: every flow names its persona, goal, entry, ordered steps, decision points, failure path and
exit; every step names a screen that exists in Screens; every decision point names where each
branch goes; every screen is named by at least one flow step. Manifest side (#550): every flow
names the `journey_ref` it expands and the `persona_ref` that journey serves; both resolve in
`readiness.json`'s `journeys[]`/`personas[]`; the persona is the one the journey record itself
names; and every resolved journey of the slice is expanded by at least one flow — a journey the
lens ignored is a GAP, because the slice's real paths must all be drawn. `--readiness` is passed on
every run; without it the manifest check silently skips, which is why the play always hands the
file over. A GAP is REC15: re-emit the Flows section from the journey records and re-run.
On any GAP, apply the matching recovery (REC2–REC10, REC15) and re-run before the checkpoint — a
content-eval fail (SE-3) is REC4: rewrite the failing section to the judge's cited fixes and
re-judge until the gate passes.

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

**Step 3 — Persist (keyed, in place — decision)** · Owner: play · Depends on: Step 2
Write-then-review (ADR 026): the FULL model delta is written to the live model BEFORE the
checkpoint, so the guard, the change-shape, and the human all see the real delta. The `ux.md` is
already on the live model (Step 1). `persist_ux.py` now writes the SHARED file — the visual-core
decision — in place, keyed to `--slice-ref`: it reads the manifest's `decision_delta` and writes
the decision `skip-if-exists` under the target slice's `decisions/` folder ONLY, and it REFUSES a
decision path outside that folder (the node-level containment the file-level guard cannot provide)
and never edits an accepted decision in place. It also confirms the live `ux.md` landed. No draft,
no doc copy. Nothing is COMMITTED yet — the commit (Step 6) happens only after the gate approves;
on cancel the whole delta is reverted (Step 5):

```
python3 scripts/persist_ux.py --ux-manifest <working>/ux-manifest.yaml \
        --product-base <product_base> --slice-ref <domain>/<slice> \
        --lens-rel product-os/<domain>/slices/<slice>/lens/ux.md \
        --out-manifest <working>/persist-manifest.json
```

The persist record stamps `applied: true` (the live `ux.md` is confirmed and the decision handled)
— D1/D2's input.

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

**Step 4 — Guard the full delta + classify the shape** · Owner: play · Depends on: Step 3

**The run's write scope (the per-play guard policy, ADR 026).** The old `apply_ux.py` encoded /ux's
write scope by construction — the lens re-derive (overwrite) and decisions skip-if-exists; under
direct-model-write that same scope is the `scoped_write_guard.py` policy. Resolve `<slice-dir>` as
`<domain>/slices/<slice>`:

    --allow    'product-os/<slice-dir>/lens/ux.md'            # the ux lens (re-derive; overwrite allowed)
    --allow    'product-os/<slice-dir>/lens/screens.yaml'     # the wireframe record (re-derive)
    --allow    'product-os/<slice-dir>/lens/wireframes.html'  # the rendered page (regenerated)
    --add-only 'product-os/<slice-dir>/decisions/*'           # the visual-core + design-directive decisions (added, never modified)

**Guard ONCE over the full delta (C9).** After ALL writes (the LLM's `ux.md` from Step 1 and the
keyed persist's decision from Step 3), run the scoped guard a single time over the whole delta.
Capture its report — its `ok` field is the stop condition's D3 input (this replaces the old
before/after verify):

```
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
        --allow 'product-os/<slice-dir>/lens/ux.md' \
        --allow 'product-os/<slice-dir>/lens/screens.yaml' \
        --allow 'product-os/<slice-dir>/lens/wireframes.html' \
        --add-only 'product-os/<slice-dir>/decisions/*' \
        --out <working>/guard-report.json
```

If the guard exits non-zero (a path outside the slice's lens/decisions scope changed, or an
existing decision was overwritten), re-run with `--restore` to revert the offending paths, apply
REC9, and re-persist before the checkpoint.

**Classify the full working-tree delta (C11).** Classify the model tree's diff vs HEAD — now the
FULL delta (the `ux.md` + the visual-core decision), per ADR 026 write-then-review (no draft dir):

```
python3 scripts/classify_change.py --play ux \
        --product-base <product_base> --base-ref HEAD --out <working>/shape.json
```

**SE-9 (F2/F9/C2/C9):** the scoped-write guard report reads `ok: true` — the model delta is
confined to the slice's `ux.md` (re-derive) and its `decisions/*` (add-only); the spine, the slice
record, the profile, and the other lenses are byte-unchanged, and no accepted decision was edited
in place.

### Phase: Checkpoint (conditional gate, C11)

**Step 5 — Human review (class: standard, conditional)** · Owner: play · Depends on: Step 4
**This is the single checkpoint (C11) — the agent never skips it on its own judgment.** It is a
**conditional gate** (#467) per `standards/rules/gate-config.md` — /ux is one of the eleven
conditional document plays. Resolve it first match wins: pinned (n/a here) → `gates.plays.ux` →
the learned policy → `gates.classes.standard` → `gates.default` (absent ⇒ on). For the policy
lookup, use the shape key classified in Step 4.

Look the shape key up in the config-resolved policy (`gates.conditional.policy`, default
`.garura/core/gate-policy.yaml`): **auto-pass** iff the shape is in the policy's `auto:` block AND
not in `never_auto:` AND Step 2 + Step 4 stand with no blocking finding (a `lint_grounding.py` gap,
a `grounding_gate.py` content-eval fail, or a guard violation). On auto-pass, do NOT wait: record
`gate auto-passed by learned policy (shape: <shape-key>, policy v<version>)` as a Checkpoint
Decisions row, include the working-tree diff summary in the run record, append the crossing's
live-eval ledger line, and proceed to Step 6 (commit):

```
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play ux \
        --issue <issue> --shape <shape-key> --predicted auto --human auto_pass \
        --policy-version <policy version> --ts <run ts>
```

Anything else resolves the gate on (an explicit `gates.plays.ux: off` instead records `gate
skipped by config (<resolution path>)` as a Checkpoint Decisions row and proceeds). When on,
present the proposed screens (with layouts and the object each is about), flows, states, and
visual core, plus the decisions, **inline
over the real model git diff** — and **hand over the artifacts before asking for an answer (C11)**.
Print both on-disk paths and say plainly which one to open:

```
The design is written. Open the wireframes before you answer — that is the design:
  wireframes   <product_base>/product-os/<domain>/slices/<slice>/lens/wireframes.html
  the lens     <product_base>/product-os/<domain>/slices/<slice>/lens/ux.md
```

A summary of the work is not the work. An approval given by someone who was never offered the
artifact is the play reviewing itself, and it does not count — re-present with the paths and take
the answer again (REC11). On the auto-pass path nobody is waiting, so write both paths into the
recorded diff summary instead, so whoever reads the record later lands on the artifact rather than
on a description of it.

Then render the approval prompt
(`standards/templates/approval-prompt.md`) and wait for the typed response — typed, never a picker
or a menu. Approve → continue to
Step 6 (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" (the branch, issue, and STM start-change
created are its own committed side effects and are left as-is):

```
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
```

Then append the crossing's live-eval ledger line with the human's real action:

```
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play ux \
        --issue <issue> --shape <shape-key> --predicted gate \
        --human <approved_clean|approved_edited|rejected> --ts <run ts>
```

`<issue>` is the slice-realize issue Step 0 resolved. `<gates.conditional.ledger>` /
`<gates.conditional.policy>` resolve from config `gates.conditional` (defaults
`.garura/core/gate-evals.jsonl` / `.garura/core/gate-policy.yaml`); `<policy version>` is the
policy file's `version:` field. `<run ts>` is the run's own UTC timestamp, derived the same way the
close derives `ts` (`date -u`), passed by the orchestrator.
**SE-10 (F11/C11):** the model delta was written to the live model by Steps 1 + 1b + 3 but is COMMITTED
(made durable) only at Step 6 on approval — so no product-model change is COMMITTED before the gate
resolves (a typed approval, a recorded config skip, or a recorded policy auto-pass); on cancel the
whole working-tree delta is reverted before any commit, so nothing is left on the tree; and the
answer that resolved the gate was taken only AFTER the lens and wireframe paths were printed (on
the auto-pass path, both paths appear in the recorded diff summary) — an approval taken without the
artifact on offer is not recorded as one (REC11).
**SE-11 (F13):** every crossing of this gate appended exactly one live-eval ledger line (shape,
predicted `gate|auto`, the human's real action or `auto_pass`), and an auto-pass fired only for a
shape the policy lists in `auto:` (and not in `never_auto:`) with no blocking finding standing.

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

**Step 6 — Commit the model delta** · Owner: play · Depends on: Step 5
The gate approved (or auto-passed / was skipped by config). Commit the full model delta on the
branch (C13, ADR 026 step 7) — a lightweight persist step that makes the writes durable and
advances HEAD so the next pipeline play (/agentic) enters a clean tree; it is NOT the pipeline end
sequence (no end PR — that closes at /marketing). A cancelled checkpoint never reaches this step —
its tree was already restored in Step 5:

```
git add -- <product_base>product-os
git commit -m "feat(model): ux lens <slice> — screens, flows, states, visual core (#<issue>)"
```
**SE-12 (F12/C12):** the close is stop-condition gated — `check_stop_condition.py` over the baked
`stop-condition.yaml` (D1 the persist record `persist-manifest.json` exists; D2 it stamps
`applied: true`; D3 the captured `guard-report.json` reads `ok: true`) must read **held** before
any COMPLETED close, and the model delta is committed (C13); a run whose persist or guard did not
land closes HALTED, never COMPLETED (REC12).

### Phase: Scenario Validation

**Step 7 — Scenario evals** · Owner: play · Depends on: Step 6
- **SCE-1 (S1 — product designer):** `ux.md` is a valid UX Lens doc clearing the linter + the
  content eval, written in place on the live model, and the spine/slice/profile/other lenses are
  byte-identical (the scoped-guard report reads `ok`); the stop-condition verdict reads held.
- **SCE-2 (S2 — product owner):** every functionality the slice bundles maps to ≥1 screen, and
  every screen is reached by ≥1 flow step.
- **SCE-3 (S3 — ux researcher):** every screen traces to a functionality or persona/journey; every
  flow's `journey_ref` and `persona_ref` resolve against `readiness.json`; every role the doc names
  resolves to a persona record and every screen it declares is named by a journey step; the visual
  core to a decision that resolves, and it names a design-system reference or records that none
  exists yet.
- **SCE-4 (S4 — architect):** no other realize lens was read or written.
- **SCE-5 (S5 — product owner, re-run):** a re-run re-derives only `ux.md`; everything else
  byte-identical; no accepted decision edited in place (the keyed persist skipped the existing
  decision).
- **SCE-6 (S6 — reviewer):** the checkpoint showed the screens, flows, states, and visual core
  inline
  over the real model git diff AND printed the on-disk paths of the lens and the rendered
  wireframe page before taking an answer, so the human approved the artifact rather than a
  description of it; and no product-model change was COMMITTED before approval — on
  cancel the working tree returns byte-clean to HEAD — or, on the auto-pass path, the change shape
  is policy-listed and a recorded auto-pass + live-eval ledger line + diff summary exist, with no
  wait.
- **SCE-7 (S7 — ux researcher, reads the flows):** every screen names the one object it is
  about; every flow names persona, goal, entry, ordered steps, decision points, failure path
  and exit; every step resolves to a screen in Screens; every screen appears in at least one
  flow; `validate_ux.py --lens` reports the flow cross-check ok.
- **SCE-8 (S8 — product owner, looks at the product):** `lens/screens.yaml` and
  `lens/wireframes.html` both exist on the live model; `render_wireframes.py --check` reports no
  byte difference, so the page came from the record and was never hand-written; every journey step
  resolves to the moments covering it or carries a named gap, and the gaps are listed in the
  wireframe report; every moment holds a frame body; no frame carries a palette, type scale, or
  component library.

### Phase: Evidence & Close

**Step 8 — Close** · Owner: play · Depends on: Step 7
Run the Standard Play Close. /ux opened a slice-realize issue via start-change, so it is
project-scoped — record evidence per the D1 rule.

```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/ux/"   ; slug="#${issue}"
#     product-scoped play : evidence_base="${product_base}_evidence/ux/"        ; slug="${slice_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 "${stm_base}_realize/ux/status/session-stamp-ux.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 working root as the
# close's authoritative input.
python3 scripts/check_stop_condition.py \
    --manifest "<play-dir>/stop-condition.yaml" \
    --base "${stm_base}_realize/ux/<slice>/" \
    --out "${stm_base}_realize/ux/status/stop-condition-ux.yaml"
sc_exit=$?   # 0 held · 1 unmet · 2 error
# Conditional-gate policy refresh (#467) — soft: a distill failure never blocks the close
python3 scripts/distill_gate_policy.py \
    --ledger "$(yq '.gates.conditional.ledger' .garura/core/config.yaml)" \
    --policy "$(yq '.gates.conditional.policy' .garura/core/config.yaml)" \
    --streak "$(yq '.gates.conditional.streak' .garura/core/config.yaml)" \
    --project "$(yq '.project.name' .garura/core/config.yaml)" || true
```

**Step C0 — bind the verdict.** `sc_exit == 0` (held) permits `status: COMPLETED`.
Anything else closes `HALTED` with `exit_reason: stop_condition_unmet` and the evidence's
Stop Condition section names every unmet clause — fix the state per REC12 (re-run the keyed
persist, re-capture the scoped-write guard report, or make the model-delta commit) and
re-evaluate; the close stays HALTED until the verdict reads held. An unevaluable verdict is never
a pass.

`/ux` opened a slice-realize issue via start-change, so it is project-scoped:
`evidence_base="${stm_base}${issue}/evidence/ux/"` and `slug="#${issue}"`.

**Step C1 — Write evidence file.** Gated by the resolved `evidence.record` flag. When false, skip
and record `evidence skipped (record=false)`. Otherwise fill the `evidence-file.md` slots (play
`ux`, run_id `ux-${ts}`, slice slug, started/completed, status per C0, exit_reason; artifacts: the
slice's `ux.md`, the manifest, the visual-core decision, the persist manifest
(`persist-manifest.json`), the captured `guard-report.json`, the model-delta commit sha, the
stop-condition verdict; the content-eval verdict; the wireframe report; step + scenario evals SE-1…SE-15 / SCE-1…SCE-8;
checkpoint decision from Step 5 (incl. any `gate skipped by config` or `gate auto-passed by learned
policy` row) plus the gate ledger line(s) appended this run; 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: `## ux Delivered — ${slug}`, the Run Summary
table (incl. the stop-condition verdict), the Pipeline Steps table, the Artifacts Produced table
(the ux lens + the visual-core decision + the model-delta commit sha), Next Steps (run /agentic on
this slice), and a pointer to `$evidence_dest`. Always emitted.

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

## Scenario Validation

| Scenario | Persona | Eval |
|----------|---------|------|
| S1 — first run | product designer | SCE-1 |
| S2 — validates the shape | product owner | SCE-2 |
| S3 — grounded | ux researcher | SCE-3 |
| S4 — hub-only | architect | SCE-4 |
| S5 — re-run | product owner | SCE-5 |
| S6 — the checkpoint | reviewer | SCE-6 |
| S7 — reads the flows | ux researcher | SCE-7 |
| S8 — looks at the product | product owner | SCE-8 |

## Recovery

| For | Trigger | Direction | Handoff |
|-----|---------|-----------|---------|
| F1 | the slice is absent, a functionality does not resolve, the profile is not firmed, a surface's persona does not resolve, or a surface is reached by no journey | halt and route to /shape (it owns the persona and journey records and already guarantees a journey per surface) or /understand before /ux runs; never invent the missing persona or journey | human |
| F2 | a write touched something beyond this slice's ux.md or its visual-core decision | the guard's `--restore` already reverted the out-of-scope write; re-run writing only the slice's ux.md and its decision | autonomous |
| F3 | ux.md fails the template/shape or carries out-of-scope content | re-emit to the UX lens template (Intent/Screens/Flows/States/Visual core only; accessibility belongs to marketing, the wider cross-product journey to /story) | autonomous |
| F4 | ux.md fails the content-quality eval — not self-explaining, or naming a role that resolves to no persona record, or declaring a screen no journey step names | rewrite the failing section to the judge's cited fixes; replace an unresolvable role with the persona record it stood in for (never add the persona — /shape owns those); tie an uncovered screen to the step it serves or drop it; re-judge and re-run the grounding cross-check until both pass | autonomous |
| F5 | an invented/ungrounded element | drop it, or re-tie the screen to a functionality or persona/journey, and the visual core to a decision | autonomous |
| F6 | a functionality is covered by no screen | add the screen(s) that visualize the missing functionality | autonomous |
| F7 | /ux read or depended on another lens | remove the dependency; /ux derives only from the slice's hub | autonomous |
| F8 | the visual core was set with no decision, or a thing the design settled for the technical build sits in prose with no decision | record the slice-level visual-core decision in the manifest (reuse the product one if it exists), and lift each settled thing out of the prose into its own design-directive decision naming what it binds downstream — recording only what the design actually settled — before the keyed persist | autonomous |
| F9 | the scoped-write guard report is not ok — a non-lens/non-decision path changed, or an accepted decision was edited in place | the guard's `--restore` already reverted the offending paths; re-run writing only the slice's ux.md and its visual-core decision, after a human confirms the restore | human |
| F10 | the navigation pattern or responsive strategy has no KB learning and no recorded proposal, or /ux chose a palette or type scale | for navigation and responsive strategy, search the KB via kb-search and ground the choice, or raise a KB-learning-gap proposal; for the look, strip the chosen palette and type scale and name the project's design-system reference instead, or record plainly that none exists yet — never a KB gap, since no shelf can tell a project what its brand is | autonomous |
| F11 | the model delta was committed before the checkpoint resolved, a cancelled checkpoint left writes on the working tree, or the checkpoint took an answer without handing over the artifact paths | for the un-offered artifact, re-present the checkpoint with both paths printed and take the answer again — an approval given without the artifact on offer does not count and is not recorded as one; otherwise revert the premature commit and the working-tree writes (guard `--restore`, empty allow set) and re-present; commit only after the gate resolves | human |
| F12 | the run is about to close COMPLETED with the Done means unmet | close HALTED (`stop_condition_unmet`) with the unmet clauses named; fix the state — re-run the keyed persist, re-capture the scoped-write guard report, or make the model-delta commit — then re-evaluate; the close stays HALTED until the verdict reads held | autonomous |
| F13 | a conditional-gate crossing left no live-eval ledger line, or an auto-pass fired for a shape the policy does not list as auto (or with a blocking finding) | re-append the missing ledger line via gate_eval.py; when the auto-pass was unearned, revert any premature persist and re-run the gate as a live wait | autonomous |
| F14 | the product-os tree is dirty once start-change has cut the branch (uncommitted model edits present) | halt and ask for a clean model tree — commit or revert the pending model edits — before /ux writes the lens | human |
| F15 | a flow names no persona/goal/entry/failure/exit, a step names a screen that is not in Screens, a decision point does not say where a branch goes, a screen is reached by no flow, or a flow's journey/persona id resolves to no record readiness.json handed over | re-emit the Flows section to the fixed shape — add the missing field, rename the step to the real screen, name both branches of the fork, add the flow that reaches the orphan screen (or drop the screen if nothing reaches it), re-tie the persona to a persona/journey of the hub — then re-run `validate_ux.py --lens` until the flow cross-check reads ok | autonomous |
| F16 | a wireframe artifact is missing, the page disagrees with its record, a screen is covered by no journey step and carries no gap, a moment has no frame body, a state uses a tone outside the visual core's map, or a frame carries invented styling | author the missing part of the record — the frame body, the `covered_by` list, or the named gap where the step genuinely has no screen yet — correct any tone to one in the map, strip invented styling back to labels and content, then re-run `render_wireframes.py` so the page comes from the record; never hand-edit the page to agree with the record, re-render it; repeat until the wireframe report reads ok | autonomous |

## Pause and Resume

Steps run top to bottom. On entry, resolve config, resolve the target slice, check the status
marker, skip completed steps, reset any in-progress step to pending, and continue. A fresh start
with no marker runs everything and creates the marker at Step 1. Resuming a run that already wrote
the model doc enters a dirty tree; the pre-flight clean-tree assertion (F14) is scoped to a FRESH
start — a resume continues its own in-progress delta.

## Compilation Metadata

| Field | Value |
|-------|-------|
| fingerprint | sha256:dbb1f8b843e0073517523349f95b137aa777005e89514311c05ea2818daa8474 (of `reference/ice.md`) |
| compiled_by | play-editor (#552 wireframes drawn, visual core narrowed, design directives, artifact-handover checkpoint, truth-checking content eval); prior: play-editor (#550 persona + journey grounding), play-editor (#548 flows + object-per-screen), play-editor (#500 direct-model-write, ADR 026), play-editor (#467 Batch B, #466 Batch C) |
| pipeline_position | start (functional pipe head; the functional pipe closes at /marketing) |
| workflow_structure | A (single checkpoint — class: standard, conditional learned gate per gate-config.md #467; direct-model-write WRITE-THEN-REVIEW per ADR 026 — persist + guard + classify before the gate, commit after; stop-condition gated close) |
| stop_condition | stop-condition.yaml (D1–D4), gate live at Step C0 |
| domain_agents | 1 (product-os-keeper) |
| utility_agents | 0 |
| skills_used | kb-search, author-ux-lens |
| scripts | 13 (preflight, check_ready_slice, lint_grounding, grounding_gate, validate_ux, check_kb_grounding, persist_ux — keyed in-place persist, render_wireframes — draws screens.yaml into wireframes.html and proves the page was generated (#552), scoped_write_guard — post-write containment, check_stop_condition — Done-means gate, session_stamp — #463 identity stamp, classify_change + gate_eval + distill_gate_policy — #467 conditional gate) |
| step_evals | 15 (SE-1…SE-15) |
| scenario_evals | 8 (SCE-1…SCE-8) |
| recovery_entries | 16 (one per failure condition; 13 autonomous / 3 human) |

**Recompiled note (#552, the slice gets drawn):** five intent changes applied as one change set,
from the griffin run for `slice-trusted-local-collection`, where /ux produced one commit and the
product owner made nine correction commits after it while the play's own content check passed the
first draft on every criterion.

- **The slice is drawn, not only described** (C15, F16, REC16, S8, SE-15, SCE-8, D4). New Step 1b
  and the bundled `render_wireframes.py`. The authoring skill writes the wireframe record
  `lens/screens.yaml`; the script renders `lens/wireframes.html` from it and re-renders with
  `--check` to prove the page was generated. A frame ties to a journey step through
  `journeys[].steps[].covered_by`, never a field on the screen, so ordering follows the journey and
  a new moment renumbers nothing. A step no moment covers carries a named gap — recorded, never a
  halt, because the griffin artifact the product owner accepted kept one. A product-wide surface
  map spans slices and stays out of scope (C2).
- **The visual core fixes meaning, not the look** (C3, C5, C10, F10, REC10). It carries the
  semantic vocabulary, its tone map, the word-alongside-colour rule, and the design-system
  reference — or an explicit "none yet". /ux no longer picks a palette or a type scale, and the
  look is never a KB gap: no shelf can tell a project what its brand is.
- **Decisions widen to design directives** (C8, F8, REC8). Designing a slice settles non-visual
  things the technical design must inherit; each becomes its own slice-level decision naming what
  it binds downstream, rather than being retold by hand into /arch.
- **The checkpoint hands over the artifact** (C11, F11, REC11, SE-10, SCE-6). It prints the lens
  and wireframe paths and says which to open before it takes an answer; on the auto-pass path both
  paths go into the recorded diff summary. An approval given without the artifact on offer is not
  recorded as one.
- **The content eval checks truth, not only form** (C4, F4, REC4, SE-3, SCE-3). `validate_ux.py`
  gained `--screens` and two mechanical cross-checks: every role the doc names resolves to a
  persona record, and every screen it declares is named by a journey step. The griffin draft's "a
  general viewer" — a role that exists nowhere in the model — was flagged by the old judge as a
  wording caveat; it is now a hard fail.

Outside the play: `author-ux-lens` authors `screens.yaml` and the widened decision set, and the UX
lens template's Visual core guidance and gold example were rewritten to match. The lens heading
list is unchanged, so the ten duplicated `lint_grounding.py` copies need no sync. The slice-outcome
trace belongs to /shape (#561) and is deliberately not built here.

**Recompiled note (#500, direct-model-write / ADR 026):** migrated from draft-then-apply to
direct-model-write. The old draft model tree and the `apply_ux.py`/`check_ux.py` promotion+verify
scripts are removed; the authoring skill writes `ux.md` straight to the live model; the new keyed
`persist_ux.py` writes the visual-core decision in place (skip-if-exists, keyed to the slice);
containment is the post-write `scoped_write_guard.py` (its `guard-report.json` is D3);
`classify_change.py` reads the working-tree git diff (`--product-base`/`--base-ref HEAD`);
checkpoint cancel reverts the working tree via the guard `--restore`; the play asserts a clean
product-os tree at entry (F14) and commits its own `feat(model)` delta after approval (C13). Order
is **write-then-review** (ADR 026 "Order of operations"): the full delta — the LLM's `ux.md` AND
the keyed persist's decision — is written to the live model FIRST (Steps 1+3), then guarded ONCE
and classified over the full delta (Step 4), then the gate resolves over the real git diff
(Step 5), and only an approved gate COMMITS (Step 6). Nothing is COMMITTED before approval; cancel
reverts the uncommitted writes. See `standards/rules/direct-model-write.md`.

**Recompiled note (#467 Batch B):** checkpoint upgraded to a conditional learned gate; see
`gate-config.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` is **REQUIRED** — confirming the emitted
SKILL matches this hand-compiled body and refreshing the fingerprint. This migration mirrors the
merged /understand reference implementation (#498) and the just-migrated /vision, /grill, /learn
on this branch. Note also: `reference/ice.md` C13 distinguishes the model-delta commit from the
Standard Play Close (evidence + delivery report) that /ux still runs; the close anchor block is
retained (required by `lint-components` structural.js and the play-creator G12 emit on every play),
and the `feat(model)` commit is the separate lightweight persist step, so both coexist as C13
describes.


## Recompile note (#548, flows + the object per screen)

Intent change via `reference/ice.md` → recompile. The UX lens was **four sections and no flows**
by design ("flows are the build's to derive"); a UX researcher reading the output could find
neither the information architecture nor the task flows, so the doc read as a screen catalogue.
The lens is now **five sections** — `Intent / Screens / Flows / States / Visual core`. Each screen
names the one object the user works with there (the information architecture is carried per screen;
there is deliberately no separate IA section). Flows are per persona + goal in a fixed field order
— Persona, Goal, Entry, Steps (each naming a real screen), Decisions (each branch's destination),
Failure, Exit — so the doc reads for a researcher and parses for an agent. States now carry the
trigger that puts a screen in them.

Boundary: these flows are the paths INSIDE this slice's screens. The person's wider journey across
the product is not this lens's to write — that is the proposed `/story` play (#528).

Changed: the intent paragraph, C3 (section shape), C6 (coverage now also screen reachability),
C11 (the checkpoint presents flows), F3, REC3, S1, S6. Added: C14 (flow completeness and internal
consistency, cross-checked mechanically), F15, REC15, S7, SE-14, SCE-7. `validate_ux.py` gained an
optional `--lens` argument and the flow cross-check; every play's `lint_grounding.py` copy carries
the new `lens-ux` heading list; the UX lens template/contract and the `author-ux-lens` skill were
re-emitted to the five-section shape. Constraints 13 → 14, failures 14 → 15, scenarios 6 → 7,
step evals 13 → 14, scenario evals 6 → 7, recovery 14 → 15. Fingerprint recomputed over the edited
ICE.


## Recompile note (#550, persona + journey grounding)

Intent change via `reference/ice.md` → recompile. #548 gave the lens a Flows section and then could
not ground it: the hub was functionality docs + the profile only, the author contract passed no
persona path, the shared readiness gate resolved neither personas nor journeys, and the flow check
accepted a persona name without opening the record. So /ux invented flows that already existed in
the model.

The fix reads what /shape already wrote. A journey record is `persona_ref + surface_refs + steps` —
a persona, the surface it runs on, and the ordered steps in the user's own words. That IS a flow.
The hub now includes the slice's persona and journey records; `check_ready_slice.py` resolves them
and LOUD-FAILS on a dangling `persona_ref` or a surface no journey reaches (C1/F1); each flow
EXPANDS one journey — the journey gives the persona and the steps, /ux adds the screen per step plus
the forks, failure path and exit; and `validate_ux.py --readiness` resolves every flow's
`journey_ref` and `persona_ref` against the handed-over lists, erroring on an id that resolves to
nothing and on a journey the lens left undrawn.

Changed: the intent's hub definition, C1, C7, C14, F1, F15, S3, REC1, REC15, SE-1, SE-14, SCE-3, and
the Step 1 contract. Added: nothing — every existing element gained a clause, so constraints stay
14, failures 15, scenarios 7, recovery 15, step evals 14, scenario evals 7. `readiness.json` is now
kept on disk at pre-flight and handed to the validate step. The UX lens template, the structural
linter and the Flows field set are UNTOUCHED: the journey id lives in the manifest, not the doc.
Companion change on /shape (#550 scope B) enriches the persona record itself. Fingerprint
recomputed over the edited ICE.

Files in this skill

  • SKILL.md36.4 KB
  • reference/ice.md18.8 KB
  • scripts/check_kb_grounding.py4.8 KB
  • scripts/check_ready_slice.py5.1 KB
  • scripts/check_stop_condition.py5 KB
  • scripts/classify_change.py11 KB
  • scripts/distill_gate_policy.py6.3 KB
  • scripts/gate_eval.py3.7 KB
  • scripts/grounding_gate.py3 KB
  • scripts/lint_grounding.py17.2 KB
  • scripts/persist_ux.py5.6 KB
  • scripts/preflight.py6.4 KB
  • scripts/scoped_write_guard.py9.2 KB
  • scripts/session_stamp.py4.9 KB
  • scripts/validate_ux.py7.8 KB
  • stop-condition.yaml962 B

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.