| name | shape |
| position | none |
| description | Select what to build in one domain and compose it into deliverable verticals — confirm or prune its capabilities against the firmed profile + KB, select which of the functionalities /understand created to build now, create the personas and user journeys, and bundle the functionalities into vertical slices, each behind a user-facing surface scaffolded slice by slice. The product-owner step of the ProductOS strategy pipeline, after /vision and /understand. Selects against the profile but never writes it; composes slices but never cuts epics or plans order. Opens no delivery issue. |
| user-invocable | true |
shape
Take one domain that /understand has detailed — capabilities detail: detailed with
their functionalities created, profile set — and select what to build and compose it
into deliverable verticals. /shape is the product owner: it confirms the capabilities
that stay (prunes the rest), selects which existing functionalities to build now, creates
the personas served and the user journeys they travel, and bundles those functionalities
into the domain's vertical slices, each behind a user-facing surface. It reads the
profile to judge fit but never writes it, it never creates a functionality or authors
ICE (/understand did), and it never cuts epics (/grill) or plans order (/roadmap). One
domain per run.
Always scaffold the UI. Every slice names at least one surface a persona opens and
checks, and that surface is a thin scaffold — this slice's piece of screen, not the whole
UI. The product surface accretes slice by slice.
Pipeline position: none. /shape is a MIDDLE play of the strategy pipeline (vision → understand → shape → roadmap): it expects to run on the branch /vision already started, injects no start-change head and no close sequence, stops when its work is done, and leaves the branch as-is for the next play to pick up. The close belongs to /roadmap. It writes the persistent product model directly, in place on the already-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 record files — each slice, persona, journey, and decision
record, and the _deferred bucket — straight to the live model; the one shared-file mutation
(the spine _spine.yaml — the capability status flips, the persona/journey/decision refs,
and the slices index) is done by the deterministic keyed persist script, in place, keyed to
the domain so it cannot touch a capability outside it inside the spine. /shape never writes the
profile. The model tree is asserted clean at entry (F16) and the play commits its own model
delta at close (C15), 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 shape ICE (reference/ice.md) by play-editor
(#466 Batch C, Level 3 rollout per ADR 025; #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). Intent defines
constraints (C1–C15) and failure conditions (F1–F16); the expectation defines success
scenarios (S1–S8), a Done means (D1–D3, 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 the step order. You delegate the
domain work — the grounded selection-and-composition bundle — to the product-os-keeper
agent via a JSON contract over files on disk, and you run the mechanical work (readiness
gate, bundle validation, the keyed in-place persist, and the post-write scoped guard)
through bundled scripts. You never write the model YAML yourself, you never create a
functionality or write the product profile, you never write the spine _spine.yaml (the keyed
persist owns it), and you never COMMIT the model delta before the single checkpoint (C12)
resolves — a typed approval, a recorded config skip, or a recorded policy auto-pass.
Forbidden: hand-writing slice/persona/journey/decision records or the spine; creating a
functionality or authoring ICE (that is /understand's); writing the product profile by any
route; reparenting, renaming, or deleting nodes; editing a capability beyond its status
flip; cutting epics or planning slice order; writing the spine by any route other than
scripts/persist_shape.py; committing the model delta before Step 5 resolves; running against
a dirty product-os tree (C15/F16); closing COMPLETED without the stop-condition verdict
reading held (C14/F14).
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
product-os-keeper | Confirm/prune capabilities and select existing functionalities against the firmed profile + KB; create personas, user journeys, decisions; compose the domain's vertical slices behind scaffolded surfaces — writing each RECORD straight to the live model and emitting the spine-delta into the manifest (never the spine, never the profile) | search-kb, author-shape-bundle | Author |
product-os-keeper is the single domain agent this play uses (1 of the ≤5
budget). No utility agents are needed — git/issue machinery is absent (position none).
Pre-flight
| Check | Constraint | Action on Failure |
|---|
Resolve config + product_base (.garura/core/config.yaml) | — | Hard halt |
Profile firmed (set) AND target capabilities detail: detailed with functionalities | C1 | Hard halt (REC1) |
Clean model tree — git status --porcelain -- <product_base>product-os is empty | C15/F16 | Hard halt (REC16) |
Resolve config mechanically with the bundled resolver; /shape has no branch or issue
(position none):
python3 scripts/preflight.py --play shape --config .garura/core/config.yaml
python3 scripts/check_ready.py --spine <product_base>/product-os/_spine.yaml --domain <domain-id>
preflight.py returns config facts (product_base, stm_base, evidence_record).
check_ready.py is the readiness gate (C1): it halts unless the product profile is set
(firmed by /understand) and every target capability in the domain is detail: detailed
with at least one functionality. The target domain is the play argument (e.g.
/shape token-dash). If the profile is still directional or a capability is not yet
detailed, hard halt and route to /understand (REC1).
Clean-tree assertion (C15/F16, ADR 026). Before any work, assert the product-os tree
carries no uncommitted edits — HEAD is only a correct base for the scoped guard and the
change-shape if the tree is clean at entry:
test -z "$(git status --porcelain -- <product_base>product-os)" || { echo "HALT: dirty product-os tree (REC16)"; exit 1; }
If dirty, halt at pre-flight and ask for a clean model tree (commit or revert the pending
model edits, or run the prior pipeline play to its close) before /shape proceeds.
The run's working root (<working> below) is {stm_base}_shaping/shape/<domain>/ — the
routing, the shape-manifest.yaml, the change-shape (shape.json), 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 slice/
persona/journey/decision records and the spine) is written IN PLACE under
<product_base>product-os/, never into <working>. The stop condition evaluates against
<working>. Status markers live at {stm_base}_shaping/shape/status/.
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}_shaping/shape/status/session-stamp-shape.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}_shaping/shape/status/<domain>.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 model delta — the LLM records AND the keyed persist's
spine mutation — 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.
[T1] Author the bundle (records to live) blockedBy: []
[T2] Validate the live bundle blockedBy: [T1]
[T3] Persist (keyed, in place — the spine) 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: Author (records to the live model)
Step 1 — Author the bundle (records to live) · Owner: product-os-keeper · Depends on: pre-flight
The persisted model does not record which KB shelf a domain came from, and the product
domain slug is not the KB shelf name. So the agent first recovers the shelf with
search-kb, then invokes author-shape-bundle to author the domain's selection-and-
composition bundle — confirm/prune per capability, select which existing functionalities to
build, the personas, the user journeys through surfaces, the decisions, AND the domain's
vertical slices (each naming at least one user-facing surface a named persona opens and
checks, bundling functionalities by spine id, plus the _deferred bucket), with stable ids.
Per ADR 026 the skill writes each RECORD straight to the live model in place and emits the
spine-delta as structured data in the manifest (it never writes _spine.yaml or the profile):
{
"task": "recover this domain's KB shelf via search-kb, then SELECT among the functionalities /understand already created against the firmed profile + that shelf, and COMPOSE the domain's vertical slices. Write each slice/persona/journey/decision RECORD straight to the live model in place; emit the spine-delta (capability status flips, the slices index, the refs) as structured data in the manifest — never write _spine.yaml or the profile. Never create a functionality or author ICE. Every slice MUST name >=1 user-facing surface (a thin UI scaffold a named persona opens and checks) — a backend-only/horizontal/whole-UI slice is invalid. Journeys are user journeys ON named surfaces. Name surfaces; never design them. Every selected functionality in a slice or _deferred; slices reference functionalities by spine id; no order/effort/depends_on",
"inputs": { "domain": "<id + slug + path under product-os>",
"product_base": "<product_base>",
"manifest_path": "<working>/shape-manifest.yaml" },
"outputs": { "routing": "<working>/routing.yaml",
"manifest": "<working>/shape-manifest.yaml" }
}
The agent passes the shelf search-kb resolved to author-shape-bundle as its kb_shelf.
It returns the contract with the output paths on disk — never inline content. The skill reads
the live spine read-only, writes the records IN PLACE under <product_base>product-os/,
and writes shape-manifest.yaml under <working> (STM) with the spine-delta as structured
data. It writes NO shared model file.
SE-1 (F1/C1): the readiness gate passed at pre-flight — the profile is set and the
target capabilities are detail: detailed with functionalities; otherwise the run halted
(REC1).
SE-16 (F16/C15): the product-os tree was clean at entry — the pre-flight assertion
(git status --porcelain -- <product_base>product-os empty) passed on a fresh start; a dirty
model tree halted at pre-flight (REC16), so the change-shape and the scoped guard reflect only
this run's delta.
Phase: Validate
Step 2 — Validate the live bundle · Owner: play · Depends on: Step 1
Run the bundle validator over the LIVE records and the manifest before the persist. Under
direct-model-write the records are already written in place, and the spine has NOT yet been
mutated for this run (the keyed persist runs at Step 3). So the validator reads the live records
and resolves functionality_refs against the untouched live spine:
python3 scripts/validate_shape.py --root <product_base>/product-os \
--manifest <working>/shape-manifest.yaml --spine <product_base>/product-os/_spine.yaml
SE-2 (F2/C2): the bundle selects and composes only — it created no functionality and
authored no ICE, and wrote no profile (the records hold only slices, personas, journeys, and
decisions; the spine-delta in the manifest is status flips + slice index + refs).
SE-3 (F3/C3): grounding holds — every kept capability and selected functionality in the
manifest carries a KB shelf or a recorded proposal; none is invented.
SE-4 (F5/C5): schema + integrity — personas, journeys, decisions, and slices carry
their required fields; every slice functionality_ref resolves to a real functionality in the
live spine; every journey persona and surface reference resolves.
SE-5 (F6/C6): placement holds — every selected functionality appears in a slice or the
_deferred bucket; validate_shape.py reports no unplaced functionality.
SE-6 (F7/C7): every slice is a user-facing scaffold — it names at least one surface
(name + persona + user_action), and is no backend-only, one-per-capability horizontal, or
whole-UI slice.
SE-7 (F8/C8): surfaces are named, not designed — no slice surface carries wireframe,
component, layout, or visual-design content.
SE-8 (F9/C9): journeys are user journeys through surfaces — each traverses at least one
named surface, none is a backend pipeline, and every surface a slice names is reached by a
journey.
SE-9 (F10/C10): slices reference functionalities by spine id (not copied) and carry no
order, effort, or resolved depends_on.
SE-10 (F11/C11): a decision is recorded for every prune and every material selection.
On any GAP, apply the matching recovery (REC2–REC11) and re-run before the persist.
Phase: Persist (write the spine first, ADR 026 write-then-review)
Step 3 — Persist (keyed, in place — the spine) · 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 records
are already on the live model (Step 1). persist_shape.py now writes the SHARED file — the
spine _spine.yaml — in place, keyed to --domain: it reads the manifest's spine_delta and
flips the named capabilities' status field (active/deprecated — status field ONLY), attaches
the persona/journey/decision refs onto those capabilities (additive), and adds the new slices
index entries (skip-if-exists) — and it REFUSES to flip any capability the manifest does not
name or one outside the domain, and refuses any profile delta (this is the node-level
containment the file-level guard cannot provide). No draft, no record 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_shape.py --manifest <working>/shape-manifest.yaml \
--product-base <product_base> --domain <domain-id> \
--out-manifest <working>/persist-manifest.json
SE-12 (F4/C4): each flipped capability changed in the status field only — a prune is
deprecated, never a delete; no reparent, rename, create/delete, or other out-of-scope
capability edit (the keyed persist writes only the status field and the ref lists).
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_shape.py encoded shape's write scope by construction; under direct-model-write that
same scope is the scoped_write_guard.py policy — resolve <domain> as the domain's folder
under product-os (e.g. token-dash):
--allow 'product-os/_spine.yaml' # the shared spine (persist writes: status flips + slice index + refs)
--add-only 'product-os/<domain>/slices/*' # slice records + the _deferred bucket (added)
--add-only 'product-os/<domain>/*/personas/*' # persona records (added)
--add-only 'product-os/<domain>/*/journeys/*' # journey records (added)
--add-only 'product-os/<domain>/*/decisions/*' # decision records (added)
Guard ONCE over the full delta (C13). After ALL writes (the LLM records from Step 1 and the
keyed persist's spine write 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 check_shape.py verify):
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
--allow 'product-os/_spine.yaml' \
--add-only 'product-os/<domain>/slices/*' \
--add-only 'product-os/<domain>/*/personas/*' \
--add-only 'product-os/<domain>/*/journeys/*' \
--add-only 'product-os/<domain>/*/decisions/*' \
--out <working>/guard-report.json
If the guard exits non-zero, re-run with --restore to revert the offending paths, apply
REC13 (a functionality/domain/profile or an out-of-scope path changed), and re-persist before
the checkpoint.
Classify the full working-tree delta (C12). Classify the model tree's diff vs HEAD — now
the FULL delta (records + spine status flips + slice index + refs), per ADR 026 write-then-
review (no draft dir), so a high-impact run (many prunes and new slices) yields a materially
different shape key from a light one:
python3 scripts/classify_change.py --play shape \
--product-base <product_base> --base-ref HEAD --out <working>/shape.json
SE-13 (F13/C13): the scoped-write guard report reads ok: true — the model delta is
confined to /shape's scope (the spine plus the new slice/persona/journey/decision records); no
functionality, no domain, and no profile field changed, and no path outside the scope moved.
Phase: Checkpoint (conditional gate, C12)
Step 5 — Human review (class: standard, conditional) · Owner: play · Depends on: Step 4
This checkpoint is a conditional gate (#467) per standards/rules/gate-config.md —
/shape is one of the eleven conditional document plays. Resolve it first match wins:
pinned (n/a here) → gates.plays.shape → 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):
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 validate_shape.py gap — /shape's
lint-equivalent guard — 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 shape \
--issue <strategy 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.shape: off instead
records gate skipped by config (<resolution path>) as a Checkpoint Decisions row and
proceeds). When on, present the domain's selection bundle inline over the real model git
diff — the kept capabilities, the pruned capabilities (each with its reason), the selected
functionalities, the personas, the journeys, AND the vertical slices (each led by the
user-facing surface it exposes, with its outcome and the functionalities it bundles) plus
the _deferred bucket. Say plainly that the surface's design — and the slices' order, effort,
and dependencies — come later (/realize designs the surface; /roadmap plans the slices; /grill
cuts the epics), not here. Render the approval prompt
(standards/templates/approval-prompt.md) and wait for the typed response. 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":
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 shape \
--issue <strategy issue> --shape <shape-key> --predicted gate \
--human <approved_clean|approved_edited|rejected> --ts <run ts>
<strategy issue> is the strategy-pipeline issue the run's branch carries (opened by
/vision's start-change). <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-11 (F12/C12): the model delta is COMMITTED only after this gate resolves — a typed
approval, a recorded config skip, or a recorded policy auto-pass; Step 6 is the sole committer
and depends on this step; the full delta written in place (records + spine) shows as the branch
diff and is reverted byte-clean on cancel (ADR 026 step 6), so nothing becomes durable before
the gate resolves.
SE-15 (F15): 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 (C15, ADR 026 step 7) — a lightweight persist step that makes the writes durable
and advances HEAD; it is NOT the pipeline start/end sequence (shape is a middle play). 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): shape <domain> — slices, personas, journeys, capability status flips (#<issue>)"
SE-14 (F14/C14): 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 (C15); a run whose
persist or guard did not land closes HALTED, never COMPLETED (REC14).
Phase: Scenario Validation
Step 7 — Scenario evals · Owner: play · Depends on: Step 6
- SCE-1 (S1 — product owner, select + compose): kept capabilities are
active, the
selected (already-existing) functionalities are placed into slices, personas and journeys
are created and schema-valid, no functionality was created or changed, and the
stop-condition verdict reads held.
- SCE-2 (S2 — architect, grounding): every kept capability and selected functionality
traces to a KB shelf or a recorded proposal in the manifest.
- SCE-3 (S3 — product owner, prune is soft): a pruned capability is
deprecated with a
recorded decision and still present — not deleted.
- SCE-4 (S4 — reviewer, box and functionalities untouched): the scoped-guard report reads
ok, so the profile is untouched and the model delta shows no change to any functionality or
domain.
- SCE-5 (S5 — delivery lead, slices reference by id): each slice has a name, outcome,
acceptance intent, and functionalities referenced by a resolvable spine id, with no copied
content and no
order/effort/depends_on.
- SCE-6 (S6 — planner, full coverage): the union of all slices'
functionality_refs and
the _deferred bucket equals the selected functionalities — none unplaced.
- SCE-7 (S7 — product owner, every slice is a user-facing scaffold): every slice names at
least one surface (name + persona + user_action) with no design content; zero surface-less
slices, no one-per-capability horizontal slice; every named surface is reached by a journey.
- SCE-8 (S8 — reviewer, the checkpoint + re-run): the checkpoint showed kept/pruned
capabilities, functionalities, personas, journeys, and the slices + deferred bucket inline
over the full written delta before any COMMIT, and on cancel the working tree returns
byte-clean to HEAD — or, on the auto-pass path (a policy-listed shape), the gate resolved
with no wait and the recorded auto-pass, the appended ledger line, and the diff summary
stand in the approval's place; a re-run added no duplicate persona, journey, or slice.
Phase: Evidence & Close
Step 8 — Close · Owner: play · Depends on: Step 7
Run the Standard Play Close. /shape 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).
# --- 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/shape/" ; slug="#${issue}"
# product-scoped play : evidence_base="${product_base}_evidence/shape/" ; 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 "${stm_base}_shaping/shape/status/session-stamp-shape.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Evaluate the Done means as the close's authoritative input.
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}_shaping/shape/<domain>/" \
--out "${stm_base}_shaping/shape/status/stop-condition-shape.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 "<gates.conditional.ledger>" --policy "<gates.conditional.policy>" --streak <gates.conditional.streak> --project "<project name from config>" || true
/shape is product-scoped: evidence_base="${product_base}_evidence/shape/" and
slug="${product_slug}" (the domain slug, e.g. token-dash).
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 REC14 (re-run the keyed
persist, re-capture the scoped-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.
Step C1 — Write evidence file. Gated by the resolved evidence.record flag (global
- per-play
evidence.plays.shape; 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 shape, run_id shape-${ts},
product_slug, started_at/completed_at, status per C0, exit_reason; artifacts produced: the
capability status flips, the selected functionalities, personas, journeys, decisions, the
vertical slices (each with its user-facing surface) + deferred bucket, the shape manifest, the
persist manifest (persist-manifest.json), the captured guard-report.json, the model-delta
commit sha, the stop-condition verdict; step and scenario eval results SE-1…SE-16 /
SCE-1…SCE-8; checkpoint decision from Step 5 including kept/pruned capabilities and the slices
(or the gate skipped by config / gate auto-passed by learned policy row when the gate
resolved without a wait) 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 and output the
report: ## shape Delivered — ${product_slug}, the Run Summary table, the Pipeline
Steps table from the task DAG, the Artifacts Produced table (status flips, selected
functionalities, personas, journeys, decisions, the vertical slices + deferred bucket),
Next Steps (run /roadmap to plan the slices — order, dependencies, effort — across
domains; then /grill cuts a chosen slice into epics), and a pointer to $evidence_dest.
Always emitted; never gated.
# --- end Standard Play Close ---
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — select + compose | product owner | SCE-1 |
| S2 — grounding | architect | SCE-2 |
| S3 — prune is soft | product owner | SCE-3 |
| S4 — box + functionalities untouched | reviewer | SCE-4 |
| S5 — slices reference by id | delivery lead | SCE-5 |
| S6 — full coverage | planner | SCE-6 |
| S7 — every slice is a user-facing scaffold | product owner | SCE-7 |
| S8 — the checkpoint + re-run | reviewer | SCE-8 |
Recovery
| For | Trigger | Direction | Handoff |
|---|
| F1 | the profile is directional, or a target capability is not detailed, at start | halt and route to /understand to firm the profile and detail the capabilities first | human |
| F2 | /shape created a functionality, authored ICE, or wrote the profile | strip the over-reach — remove the created functionality/ICE, revert the profile write; detailing and the box are /understand's | autonomous |
| F3 | a selected functionality or kept capability has no KB shelf match and no proposal | ground it against the KB, or record a propose-kb-node proposal; never keep an invented selection | autonomous |
| F4 | a capability edited beyond status, a reparent/rename, a create/delete, or a hard-deleted prune | revert the out-of-scope mutation; /shape flips capability status only and prunes soft (deprecated) | autonomous |
| F5 | a persona/journey/decision/slice fails its schema, or a slice/journey reference does not resolve | re-emit the failing record to conform, and fix the reference to a real spine functionality/persona/surface | autonomous |
| F6 | a selected functionality is in neither a slice nor the _deferred bucket | place it — add it to a slice, or record it in _deferred with a reason — before persisting | autonomous |
| F7 | a surface-less/horizontal slice, or a "whole UI at once" slice | re-cut the slice vertically toward one surface a persona opens and checks, as a thin scaffold; fold or defer functionalities that serve no surface | autonomous |
| F8 | /shape designed a surface (wireframe/component/layout/visual) instead of naming it | strip the design to the surface's name, persona, and user action; design is /realize's UX lens | autonomous |
| F9 | a journey is not a user journey on a surface, or a named surface no journey reaches | rewrite the journey as the persona's steps on a named surface; ensure every slice surface is reached by a journey | autonomous |
| F10 | a slice copied a functionality's content, or carries order/effort/depends_on | replace copied content with a spine , and strip the plan fields — /roadmap plans |
Pause and Resume
Steps run top to bottom. On entry, resolve config, resolve the target domain from the
play argument or the in-progress run, 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 model
records enters a dirty tree; the pre-flight clean-tree assertion (F16) is scoped to a FRESH
start — a resume continues its own in-progress delta.
Compilation Metadata
| Field | Value |
|---|
| fingerprint | sha256:fc97705caf7b0b6d0895dc8f6e87c492fcb365a2e1d711f252734d37f4da0920 (of reference/ice.md) |
| compiled_by | play-editor (#500 direct-model-write, ADR 026); prior: play-editor (#467 Batch B, #466 Batch C) |
| pipeline_position | none |
| position_exception | middle of the strategy pipeline — runs on the branch /vision started; the close belongs to /roadmap (#437) |
| workflow_structure | A (single checkpoint — class: standard, conditional 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–D3), gate live at Step C0 |
| domain_agents | 1 (product-os-keeper) |
| utility_agents | 0 |
| skills_used | search-kb, author-shape-bundle |
| scripts | 9 (preflight.py, check_ready.py, validate_shape.py, persist_shape.py, scoped_write_guard.py, classify_change.py, gate_eval.py, distill_gate_policy.py, check_stop_condition.py, session_stamp.py) |
| step_evals | 16 (SE-1…SE-16) |
| scenario_evals | 8 (SCE-1…SCE-8) |
| recovery_entries | 16 (one per failure condition; 12 autonomous / 4 human) |
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/check promotion scripts
(apply_shape.py, check_shape.py) are removed; the authoring skill writes each per-node
record (slice/persona/journey/decision + the _deferred bucket) straight to the live model;
the new keyed persist_shape.py writes the shared spine (status flips, slice index, refs) in
place, keyed to the domain; 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 (F16) and commits its own
feat(model) delta after approval (C15). Order is write-then-review (ADR 026 "Order of
operations"): the full delta — LLM records AND the keyed persist's spine mutation — 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 becomes durable 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.
Note also: reference/ice.md C15 distinguishes the model-delta commit from the Standard Play
Close (evidence + delivery report) that /shape 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 C15
describes.