Shape the one slice that shows a business intent is served — the first play of the shaping loop (ADR 049: /shape-slice, then /shape-plan, then /shape-eval). Run as /shape-slice <intent-issue> (for example /shape-slice 788). It reads the intent from the product model (and writes the intent page from the issue when the model has none), reads the model and memory first, and talks to the person in one confirmation brief: the slice, what it fills, its hidden file, what is left out, each assumption...
Installs into .claude/skills of the current project.
Are you the author of Shape Slice?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/intent-driven-ai-shape-slice)
---
name: shape-slice
position: both
argument-hint: "<business intent issue number> [--edit: <what to change, in your own words>]"
description: 'Shape the one slice that shows a business intent is served — the first play of the shaping loop (ADR 049: /shape-slice, then /shape-plan, then /shape-eval). Run as /shape-slice <intent-issue> (for example /shape-slice 788). It reads the intent from the product model (and writes the intent page from the issue when the model has none), reads the model and memory first, and talks to the person in one confirmation brief: the slice, what it fills, its hidden file, what is left out, each assumption and only the questions neither the model nor memory answers, within the configured question tier, every question and answer kept in the issue''s short-term memory. It asks first only for a gap that blocks the slice and that no one but the person can answer, never the surface. It picks the features from any capability in build order, the one surface and the person who tests it; reuses a slice that already serves the intent (pulling a feature in or pushing one out so it stays testable end to end), and never reshapes a slice in progress; adds or fills every domain, capability, feature or ICE the slice needs, each page in its folder, and proposes a thin ICE, with both thin marks, for a part that has none; writes the slice into the spine with its link to the intent and its record; and writes the slice''s hidden file of failure and recovery conditions in the home plan''s evals folder. A proposed answer always names its source; where none exists the question carries no proposed answer, and the count is shown. The model checks must pass for the slice''s own pages (older problems elsewhere are warnings), and nothing is committed before the person types yes (stop undoes every write). With `--edit: <their own words>` it edits the slice that exists instead, also one in progress (the brief says so) (/shape-slice #788 --edit: <words>): only the slice record''s name, outcome or tester''s action, named lines of the hidden file, or a part''s spine one-line, after the person sees each change old and new and types yes; a check refuses any other change, and a history line keeps who asked, when and their words. Writes no plan, epic, story or eval. Position both: it opens its own change and lands it through commit-change, propose-change, check-change and merge-change, on the plan''s branch when the intent has a plan.'
user-invocable: true
---
# shape-slice
A person names a business intent. /shape-slice shapes the **one slice** that a person can
test end to end to show the intent is served: an ordered group of features, taken from any
capabilities, with one surface (a screen, a command, an endpoint) and the one person who
tests it there. It fills the product model with what that slice needs, proposes a thin ICE
for each part that has none, writes the slice with its link to the intent (the spine's
`intents`, ontology v11), and writes the slice's **hidden file** of failure and recovery
conditions. It writes no plan, no epic, no story and no eval: `/shape-plan` (#818) and
`/shape-eval` (#798) come next.
The run works from the product model, never from the issue text alone: the issue is only
the way in. It reads the model and memory first, proposes what it can, and shows the person
**one brief**: the slice, what it fills, the hidden file, what is left out, each assumption,
and only the questions neither the model nor memory answers, inside the question tier set in
config. It asks before it shapes the slice only for a gap that blocks the slice and that no
one but the person can answer; the surface is never such a gap.
A person can also **edit** a slice that exists: `/shape-slice #<intent-issue> --edit: <their
own words>` (C20). The run does not shape the slice again. It turns the words into an exact
list of changes, shows each change old and new, and after the person's yes writes only that
list, with a history line of who asked, when and their words. The edit path is its own phase
below.
**Pipeline position: both.** start-change opens the change at the head (on the plan's
sub-branch when the intent has a plan); the end sequence commit-change → propose-change →
check-change → merge-change lands it.
## Compiled From
This play was compiled from the shape-slice ICE (`reference/ice.md`) by create-play (#795)
and changed by edit-play (#842, #908, #798). Intent defines constraints (C1–C20) and failure
conditions (F1–F20); the expectation defines success scenarios (S1–S12), a Done means (D1–D6, baked to
`stop-condition.yaml`), and one recovery entry per failure condition. To modify this play,
update `reference/ice.md` and recompile with edit-play.
Do NOT edit this file manually — it is a compiled artifact.
## Role
You are the orchestrator. You own the step order, the one brief, and the checkpoint. The
`product-os-keeper` agent gathers context and the skills do the work (C19). The agent reads
the model, copies memory into short-term memory through the memory tool, calls the skills
each JSON contract names, passes each skill's output on as it is, and records the trace of
each decision with `memory.py cite`. It never chooses, drafts, decides or writes content.
Every choice, draft and decision is made by a named skill: `author-business-intent`
(one-intent mode) drafts a missing intent page; `find-work-placement` places the intent's
outcome; `find-slice-gaps` writes the gaps as questions; `propose-answers` drafts the
proposed answers; `author-slice-brief` writes the openings of the early and the confirmation
brief; `author-slice` chooses the slice (its surface, its tester, its features in build
order, what is left out, what it needs from the model), decides what a reused slice pulls in
or pushes out, fixes its own records, and turns an edit's words into the list of changes; `author-domains`, `enrich-capability-ice` and
`enrich-grounding` write the pages; `author-failure-recovery` drafts the hidden file's
recoveries. You run all mechanical work through bundled scripts: reading the issue (`platform_adapter.py`), the intent page
(`write_intent_page.py`), the question record (`record_questions.py`), the question tier
(`grill_budget.py`), the serving check (`find_serving_slice.py`), the writes
(`persist_fills.py`, `persist_slice.py`, `write_hidden_file.py`), an edit's copies, writes and
before-and-after check (`edit_slice.py`), the model checks
(`slice_check.py`, which runs `lint_grounding.py`), the write guard
(`scoped_write_guard.py`), the trace check (`trace_check.py`), the brief
(`render_brief.py`), the checkpoint record and the commit gate (`record_approval.py`) and the
stop condition; every script writes through `safe_write.py`. The play never writes the
spine, a slice record, a model page or the hidden file by hand. The play reads no memory: the
agent finds memory only through the memory tool
(`python3 .garura/core/tools/memory/memory.py`, `.claude/rules/garura-memory.md`) and records
the trace of each decision with its `cite` command.
**Forbidden:** working from the issue text alone, or stopping because the model has no page
for the intent (C1); asking what the model or memory answers (C2); a list of questions in
place of the one brief, asking first about a gap the run can answer (the surface above all),
or a question that breaks the rule for asking a person (C3); asking more than the tier allows
beyond the blocking questions, or a gap left open with no written assumption (C4); a question
or answer not recorded, or an answered question asked again (C5); a slice with no tester, no
surface or more than one surface (C6); a new slice for an intent a slice already serves, a
shaping run's change to a slice in progress, a reused slice that can no longer be tested end to end, or a kept slice's new text with no history line or no epic named as out of date
(C7); skipping or leaving unfilled a part the slice needs, a page outside its folder, or a
moved or used page left on an old rule (C8); writing a plan, an epic, a story, an eval or any
file outside the allowed set, under `.garura/project/plans/` above all (C9); a proposed
answer with no named source, or a source not read through the memory tool (C10); going on
while the slice's own pages or a new problem fail the check (C11); committing before the
person's typed yes, a stop that leaves writes, or an undo that removes a line that was there
before (C12); landing outside the change plays or on the wrong branch (C13); a real person's
name, or text a person cannot follow (C14); a hidden file that is missing or leaves out a
line or a recovery (C15); a part with no ICE, or a thin ICE with one mark only (C16); a brief
that hides a result of the run (C17); reading the rule for asking a person from a place other than `_shared/asking-a-person.md` (C18);
an agent that chooses, drafts, decides or writes content, or reshapes a skill's output, or a
dispatch that names no skills (C19); on an edit, a change that is not on the list the person
approved, a write before their yes, a change to a part an edit may not change, or an edit with
no history line (C20).
**Agent boundaries:**
| Agent | Domain | Skills it uses | Phases |
|-------|--------|----------------|--------|
| `product-os-keeper` | Gather context and pass it on (on an edit: the person's words and the copies of the slice's records, to `author-slice` in edit mode; then the trace of each change): read the intent page, the spine and the pages they reach; copy memory into short-term memory through the memory tool (the domain shelves, the grounding templates, the recovery rule); pass on the shared rule for asking a person (`_shared/asking-a-person.md`, beside the installed skills, not memory) and say when it is missing; call the skills each contract names and pass each output on as it is; read each source through the memory tool and record the trace of each decision (`memory.py cite`) | `author-business-intent`, `find-work-placement`, `find-slice-gaps`, `propose-answers`, `author-slice-brief`, `author-slice`, `author-domains`, `enrich-capability-ice`, `enrich-grounding`, `author-failure-recovery` | Intent, Gather, Ask first, Shape, Reuse, Hidden file, Check, Trace, Brief, Edit |
| `template-manager` (utility) | Hand out the approval prompt and the close templates (evidence file, delivery report, Next-line map) into short-term memory | — | Checkpoint, Close |
`product-os-keeper` is the single **domain agent**; `template-manager` is the one utility
agent. Every skill the domain agent uses exists: `author-slice` (#838), `find-slice-gaps`,
`author-slice-brief`, `enrich-grounding` and `author-failure-recovery` were built for this
play (#842). Each JSON contract below names its skills in `"skills"`; only the live re-cite of
Step 10b and the edit's trace (Step X5) name none, because they call the memory tool only. A copy from one shape to another
(the proposals into the questions, the questions into the early brief) is a script's. The
start and end sequence are injected sub-plays; everything else is a script.
## Pre-flight
| Check | Constraint | Action on Failure |
|-------|-----------|-------------------|
| Resolve config, `stm_base`, `product_base` (`.garura/core/config.yaml`) | — | Hard halt |
| A business intent issue number was given (`/shape-slice <intent-issue>`, digits only) | C1 | Hard halt — ask for the intent's issue number |
| The product model's spine exists (`<product_base>product-os/_spine.yaml`) | C11 | Hard halt — run the model plays first |
| On an edit (`--edit:` in the arguments): the words after `--edit:` are not empty | C20 | Hard halt — ask what to change, in their own words |
| **Clean model tree** — after Step 0: `git status --porcelain -- <product_base>product-os` is empty | C12 | Hard halt (REC12) |
| **Clean plans tree** — after Step 0: `git status --porcelain -- .garura/project/plans` is empty, so a restore never takes back a person's own plan edits | C9, C15 | Hard halt (REC12) |
Resolve the facts with the bundled resolver, not by inference:
```
git branch --show-current
python3 scripts/preflight.py --play shape-slice --config .garura/core/config.yaml \
--branch "<current branch>"
```
`preflight.py` gives `stm_base`, `product_base` and `evidence_record`. The change's own
issue comes from start-change (Step 0), not from the branch name: the intent issue and the
change issue are two issues. After Step 0, with `<issue>` = the change issue:
```
python3 scripts/session_stamp.py --phase start \
--marker "{stm_base}<issue>/status/session-stamp-shape-slice.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
W={stm_base}<issue>/context/shape-slice
test -f $W/start-sha.txt || git rev-parse HEAD > $W/start-sha.txt
test -f $W/spine-at-start.yaml || cp <product_base>product-os/_spine.yaml $W/spine-at-start.yaml
```
`start-sha.txt` and `spine-at-start.yaml` are written once, by the run's first pre-flight: a
resume finds them and keeps them as they are, because pre-flight runs again on a resume.
`spine-at-start.yaml` is the spine as the run found it; Step 8 reads from it whether the intent
is new to a kept slice, so a step run twice, or after a resume, gives the same result (C7).
Names used below: `<intent>` = `bi-<intent-issue>` (the business intent's id),
`<model>` = `<product_base>product-os`, `<plans>` = `.garura/project/plans`,
`<working>` = `{stm_base}<issue>/context/shape-slice/`,
`<record>` = `{stm_base}<issue>/context/grounding-questions.md` (the question record,
`standards/rules/user-questions.md`). The stop condition reads `{stm_base}<issue>/`.
**Resume check:** if `{stm_base}<issue>/status/shape-slice.json` exists, resume — skip
completed steps, reset an in-progress step to pending, continue from the first incomplete one.
## Task DAG
Create ALL tasks right after pre-flight, before any domain work. The play owns this DAG.
```
[T0] start-change (injected — start) blockedBy: []
[T1] Take the model check; read the intent from the model blockedBy: [T0]
[T2] Gather the model and memory; list the gaps blockedBy: [T1]
[T3] Propose answers; check them blockedBy: [T2]
[T4] Drop answered questions; apply the tier blockedBy: [T3]
[T5] Ask first only a gap no one but the person can answer blockedBy: [T4]
[T6] Shape the slice and fill the model blockedBy: [T5]
[T7] Reuse a slice that already serves (C7) blockedBy: [T6]
[T8] Persist the fills and the slice blockedBy: [T7]
[T8b] Write the hidden file blockedBy: [T8]
[T9] Check the model blockedBy: [T8b]
[T10] Guard the writes blockedBy: [T9]
[T10b] Record the live decisions; check their trace blockedBy: [T10]
[T11] The one brief and the checkpoint (yes / change / stop) blockedBy: [T10b]
[T12] Commit the model delta and the hidden file blockedBy: [T11]
[E1] commit-change (injected — end) blockedBy: [T12]
[E2] propose-change (injected — end) blockedBy: [E1]
[E3] check-change (injected — end) blockedBy: [E2]
[E4] merge-change (injected — end) blockedBy: [E3]
[T13] Scenario Validation blockedBy: [E4]
[T14] Close blockedBy: [T13]
```
Mark each task in progress before its step and completed right after its eval passes. No
runtime reordering. A **change** at the checkpoint sends the run back to T6; T6–T11 run again
in order.
**An edit run** (`--edit:` in the arguments, C20) has its own DAG. It does not shape the slice,
so T1–T12 are not created:
```
[T0] start-change (injected — start) blockedBy: []
[X1] Read the slice's records blockedBy: [T0]
[X2] Turn the person's words into the list of changes blockedBy: [X1]
[X3] The edit brief and the checkpoint (yes / change / stop) blockedBy: [X2]
[X4] Apply the list; check before against after blockedBy: [X3]
[X5] Trace each change to the person's words blockedBy: [X4]
[X6] Commit the edit blockedBy: [X5]
[E1] commit-change (injected — end) blockedBy: [X6]
[E2] propose-change (injected — end) blockedBy: [E1]
[E3] check-change (injected — end) blockedBy: [E2]
[E4] merge-change (injected — end) blockedBy: [E3]
[T13] Scenario Validation blockedBy: [E4]
[T14] Close blockedBy: [T13]
```
A **change** at the edit checkpoint sends the run back to X2; X2–X3 run again in order.
## Workflow
### Phase: Start (injected — D2 position: both, head)
**Step 0 — start-change** · Owner: `start-change` (sub-play) · Depends on: pre-flight
Run the start-of-pipeline member as a sub-play with `parent_run_id`. It resolves or creates
the change issue, cuts its branch (the plan's sub-branch when the intent has a plan,
`.garura/project/plans/<intent-issue>.yaml`), and inits short-term memory. Then assert the
clean model tree and the clean plans tree (hard halt if dirty — REC12).
{ "play": "start-change", "parent_run_id": "<this run id>",
"inputs": { "title": "[TASK] shape-slice: shape the slice for business intent #<intent-issue>" },
"outputs": { "result": "<working>start-change.json" } }
### Phase: Intent
**Step 1 — Take the model check; read the intent from the model** · Owner: play (+ `product-os-keeper` when the page is missing) · Depends on: Step 0
First take the model check once, before the run's first write, so a problem the run did not
make is never charged to it (C11):
```
python3 scripts/slice_check.py baseline --root <model> --spine <model>/_spine.yaml \
--out <working>lint-before-run.json
```
Then read the issue through the code-host adapter, and look for the intent's page in the
model:
```
python3 scripts/platform_adapter.py --verb view-issue --arg issue_number=<intent-issue> \
> <working>intent-issue.json
python3 scripts/write_intent_page.py --root <model> --intent <intent> --date <today, UTC> \
--out <working>intent-page.json
```
Exit 0 means the model has the page: the run reads the intent from it. Exit 3 means the
model has no page: the run does not stop (C1). Dispatch the agent to have the page drafted
from the issue, then write it:
{ "task": "copy the ontology's Business Intent kind (the config's product.ontology) into short-term memory through the memory tool; call author-business-intent in one-intent mode with the issue as the one plain statement, the intent id and that copy as ontology_path; pass the skill's intent-draft.json on as it is. Gather and pass on only; the skill drafts",
"inputs": { "issue": "<working>intent-issue.json", "intent_id": "<intent>" },
"outputs": { "draft": "<working>intent-draft.json" },
"skills": ["author-business-intent"],
"task_id": "shape-slice-intent" }
```
python3 scripts/write_intent_page.py --root <model> --intent <intent> --date <today, UTC> \
--draft <working>intent-draft.json --out <working>intent-page.json
```
Tell the person in the brief that the page was written from the issue (C1).
**SE-1 (F1/C1):** `intent-page.json` reads `ok: true`, and `<model>/intents/<intent>.md`
exists; every later contract names that page as the intent's source, never
`intent-issue.json` alone; when the page was missing, `intent-page.json` reads
`written: true` and the run did not stop.
### Phase: Gather
**Step 2 — Gather the model and memory; list the gaps** · Owner: `product-os-keeper` · Depends on: Step 1
{ "task": "gather what the product model and memory hold for this business intent, and have the gaps neither answers written as questions. Read the intent page, the spine and the pages of the domains, capabilities, features, ICE, personas and journeys it reaches. Look up Garura memory and Phoenix memory only through the memory tool, never by a file path: python3 .garura/core/tools/memory/memory.py search --issue <issue> --pattern <topic> --ignore-case (add --tier garura or --tier phoenix to go up a tier), then memory.py read --issue <issue> --tier <tier> --path <path> --start <n> --end <m> for each line you use; a tier_missing error is said, never read as 'nothing found'. Copy what you read into short-term memory. The rule for asking a person is not memory: it is the shared file _shared/asking-a-person.md that the install lays beside the installed skills (../_shared/asking-a-person.md from this play's folder). Pass that path on as asking_rule and record it in gather.json as rule {path: _shared/asking-a-person.md}; never look for it through the memory tool and never read another copy. When the file is missing, write gather.json rule_missing {path: _shared/asking-a-person.md}, and the skills use the floor this play states (an opening that says what the person decides; one thing per question; Question, Answer, Why it matters, Example; examples from several domains; each question stands alone; plain words; personas); do not stop. Copy every domain shelf (**/knowledge/domains/*.md) into short-term memory and call find-work-placement with the intent's outcome as the work and the copies as domains_dir; pass its routing result on as placement.json. Then call find-slice-gaps (find mode) with the intent page, gather.json, placement.json, the record and asking_rule; pass its questions.json on as it is. The surface and the tester are never a question: find-slice-gaps writes none, and author-slice proposes them in Step 6. Gather and pass on only; the skills write the placement and the questions",
"inputs": { "intent_page": "<model>/intents/<intent>.md", "spine": "<model>/_spine.yaml",
"product_base": "<product_base>", "record": "<record>" },
"outputs": { "gather": "<working>gather.json", "placement": "<working>placement.json",
"questions": "<working>questions.json",
"asking_rule": "../_shared/asking-a-person.md" },
"skills": ["find-work-placement", "find-slice-gaps"],
"task_id": "shape-slice-gather" }
`gather.json` names every model page and memory line it read (tier, path, lines and the
fingerprint from each `memory.py read` answer), and the rule for asking a person (`rule`, with
its path) or `rule_missing`. The skills read only what the agent put in short-term memory, and
the shared rule at `_shared/asking-a-person.md`.
**SE-22 (F18/C18):** `gather.json` shows the rule for asking a person at its path
`_shared/asking-a-person.md` beside the installed skills, never found through the memory tool,
or `rule_missing` with that path; in that case the run did not stop and `brief.md` holds the
line that says the rule is missing (Step 11).
**Step 3 — Propose answers; check them** · Owner: `product-os-keeper`, then play (script) · Depends on: Step 2
{ "task": "call propose-answers with questions.json as the gaps (one gap per question, its id as the group), gather.json and its short-term copies and the record as the sources, and the intent page as the run; pass its proposals.json on as it is. The skill drafts each answer from the model, memory or a person's earlier answer only, with a basis {kind: model|memory|person, ref: <page and lines, or the question id answered>}, and gives no proposed answer where nothing backs one. Then, for each proposal whose basis kind is model, memory or person, read its source through the memory tool and take the tier, path, lines and fingerprint from that read answer: a model page from Garura memory (memory.py read --issue <issue> --tier garura --path product/product-os/<page> --start <n> --end <m>); a person's earlier answer from this issue's short-term memory (memory.py read --issue <issue> --tier short-term --path <issue>/context/grounding-questions.md --start <n> --end <m>); memory from the tier that answered. Record each with its trace (.claude/rules/garura-memory.md): python3 .garura/core/tools/memory/memory.py cite --request '{...}' with issue, play shape-slice, question (the question text), answer (the proposal's answer), decided_by propose-answers (via product-os-keeper), and a trace whose source is that read answer (tier, path, start, end, fingerprint) — the exact lines, never a whole file; list the cites in citations. Gather and pass on only; never change a proposal",
"inputs": { "questions": "<working>questions.json", "gather": "<working>gather.json",
"record": "<record>", "intent_page": "<model>/intents/<intent>.md" },
"outputs": { "proposals": "<working>proposals.json", "citations": "<working>citations.json" },
"skills": ["propose-answers"],
"task_id": "shape-slice-propose" }
Then the scripts copy the proposals into the questions and apply the three rules before
anything is shown:
```
python3 scripts/record_questions.py merge-proposals --questions <working>questions.json \
--proposals <working>proposals.json --out <working>questions.json
python3 scripts/record_questions.py proposals --questions <working>questions.json \
--out <working>questions-checked.json
```
`merge-proposals` sets a question's `proposed_answer` and `basis` only from a proposal whose
basis kind is model, memory or person with a ref; it sets `settled` and `settled_basis` from
a settled proposal; any other proposal leaves no proposed answer. It refuses (exit 2, nothing
written) a proposal for an id that is not a question.
It moves each settled gap out (it is never asked; the model or memory answer is used, C2),
strips each proposed answer that has no named basis — the question then carries
**no recommendation**: it is shown with no proposed answer, never with a substitute (C10, the
cardinal rule, `standards/rules/no-unbacked-recommendation.md`) — and counts the questions
with no proposed answer. Exit 1 lists the questions that break the shape (REC3): dispatch the
agent to have find-slice-gaps rewrite only those, then run `merge-proposals` and `proposals`
again:
{ "task": "call find-slice-gaps in rewrite mode with questions.json, the shape_problems of questions-checked.json and asking_rule; it rewrites only the questions the script refused and keeps every other question as it is; pass its questions.json on as it is. Gather and pass on only",
"inputs": { "questions": "<working>questions.json",
"problems": "<working>questions-checked.json",
"asking_rule": "../_shared/asking-a-person.md", "gather": "<working>gather.json" },
"outputs": { "questions": "<working>questions.json" },
"skills": ["find-slice-gaps"],
"task_id": "shape-slice-rewrite" }
**SE-2 (F2/C2):** no question left in `questions-checked.json` is one the model or memory
answers: each settled gap is in `settled` with its basis, and `gather.json` shows the model
and memory were read before any question was written.
**SE-3 (F10/C10):** no unbacked proposed answer remains — every non-null `proposed_answer`
in `questions-checked.json` has a basis of kind model, memory or person with a named ref,
and equals the `answer` of its proposal in `proposals.json` (copied by `merge-proposals`,
never by hand); no stripped answer was replaced by a default, a generic pointer or a substitute; each cite in
`citations.json` names a source read through the memory tool (a model page with `--tier
garura`, a person's answer with `--tier short-term`); and `no_proposed_answer` equals the
number of questions with a null proposed answer. The count is shown to the person in the
brief.
**Step 4 — Drop answered questions; apply the tier** · Owner: play (scripts) · Depends on: Step 3
```
python3 scripts/record_questions.py filter --record <record> \
--questions <working>questions-checked.json --out <working>questions-open.json
python3 scripts/record_questions.py open-list --open <working>questions-open.json \
--out <working>questions-open-list.json
python3 scripts/grill_budget.py tier --config .garura/core/config.yaml > <working>budget.json
python3 scripts/grill_budget.py split --questions <working>questions-open-list.json \
--budget <budget from budget.json> > <working>split.json
```
`filter` reads the `proposals` output as it is (its `questions`) and drops each question
already answered in the record — matched by its question text, not by its id alone —
because an answer is a fact and is not asked again (C5). A different question that reuses an
id the record holds is kept under a new id (`renamed`). `open-list` writes the open list the
budget split reads. `grill_budget.py` asks every blocking question first and fills the rest
of the budget in order; each question not asked becomes an assumption (C4).
**SE-4 (F4/C4):** in `split.json`, the asked questions that are not blocking number at most
the budget minus the blocking ones; every blocking question is asked; every question not
asked is in `assumptions` with its `assume` text.
**SE-5 (F5/C5):** no id in `split.json` `asked` is answered in `<record>` before this run,
and `filter` read `questions-checked.json` with no file made by hand.
### Phase: Ask first (only what no one but the person can answer)
**Step 5 — Ask first only a gap no one but the person can answer** · Owner: play · Depends on: Step 4
First write the whole round to the record, so nothing asked is lost, then split off the
questions that must be asked before the slice is shaped:
```
python3 scripts/record_questions.py add --record <record> --issue <issue> \
--split <working>split.json --at <now, UTC> --run <this run id>
python3 scripts/record_questions.py early --split <working>split.json \
--early-out <working>early-split.json --rest-out <working>brief-split.json
```
A question is asked first only when it blocks the slice, has no proposed answer, and is not
about the surface or the tester (C3). Every other question waits for the one brief. When
`early-split.json` asks nothing, go to Step 6. Otherwise dispatch the agent to have the
opening written, by the rule for asking a person (`_shared/asking-a-person.md`, beside the
installed skills):
{ "task": "call author-slice-brief with kind early, the intent page, early-split.json, gather.json and asking_rule; it writes the opening of the early brief, two or three plain sentences that say what the person decides and what happens with the reply; pass its early-opening.md on as it is. Gather and pass on only",
"inputs": { "kind": "early", "intent_page": "<model>/intents/<intent>.md",
"split": "<working>early-split.json", "gather": "<working>gather.json",
"asking_rule": "../_shared/asking-a-person.md" },
"outputs": { "opening": "<working>early-opening.md" },
"skills": ["author-slice-brief"],
"task_id": "shape-slice-early-opening" }
The script builds the rest of the early brief from `early-split.json`: `## Questions` with
each early question as `### <id>` and its **Question**, **Answer** ("no proposed answer
yet"), **Why it matters** and **Example** (several domains). Check it, show it, and record
the reply:
```
python3 scripts/render_brief.py --early --working <working> \
--opening <working>early-opening.md --split <working>early-split.json \
--out <working>early-brief.md
python3 scripts/record_questions.py brief --brief <working>early-brief.md \
--split <working>early-split.json --early
python3 scripts/record_questions.py answers --reply <working>early-reply.md \
--split <working>early-split.json --out <working>early-answers.json
python3 scripts/record_questions.py answer --record <record> \
--answers <working>early-answers.json --at <now, UTC> --run <this run id>
python3 scripts/record_questions.py check --record <record> --split <working>early-split.json
```
Save the reply word for word to `<working>early-reply.md`. `check` exits 1 while an early
question has no answer: ask it again; the run never guesses. A reply that starts with `skip`
and says more is listed in `answers`' `unclear` and asked again. A brief check that fails on the opening goes back to the
`shape-slice-early-opening` contract; the questions part is the script's and is not edited by
hand.
**SE-24 (F3/C3):** every question in `early-split.json` is blocking, has no proposed answer and
is not about the surface or the tester; when it asks any, `early-brief.md` was written by
`render_brief.py --early` from `early-opening.md` (the skill's output) and `early-split.json`,
and `record_questions.py brief --early` read `ok: true` for the early brief that was shown; no
other question was shown before Step 11.
### Phase: Shape
**Step 6 — Shape the slice and fill the model** · Owner: `product-os-keeper` · Depends on: Step 5
{ "task": "have the one slice that shows this business intent is served shaped, and the model filled. First call author-slice (shape mode) with the intent page, gather.json, placement.json, the record, split.json, the spine and the person's change on a re-run; it chooses the one surface and the persona who tests it there, the features from any capability in build order with why_this_order, left_out, and the needs list, and writes slice.json and fills.json, or no-slice.json with its reason when no surface can be tested end to end. Pass its files on as they are. When no-slice.json is written, stop: call no other skill and write nothing to the model. Otherwise have each page fills.json names written, in its folder: for each new domain, copy the grounding templates and the spine schema (**/standards/schemas/product-os/grounding/*, **/standards/schemas/product-os/spine.yaml) into short-term memory and call author-domains with profile: false; for each new capability, copy its domain shelf and the same schemas, and call enrich-capability-ice with depth: directional, its fills.json entry as the capability, the features fills.json gives it, and the record as the person's answers; for each new feature under a capability that exists (mode new-feature), each update with page: true (mode update) and each part in fills.thin (mode thin), call enrich-grounding with the templates' copy, the intent page, the record, split.json and fills.json. Pass each manifest and grounding-report.json on as it is; a gap a skill returns as data stays in its manifest. Then, for each entry of slice.json decisions, read each of its sources through the memory tool and record it with its trace (.claude/rules/garura-memory.md): python3 .garura/core/tools/memory/memory.py cite --request '{...}' with issue, play shape-slice, the entry's question and answer, decided_by author-slice (via product-os-keeper), and a trace of steps (asked, found, source {tier, path, start, end, fingerprint} from the read answer) — one cite each for the features chosen and their order, the surface, and the tester; a decision that rests on a person's answer names its record entry; list the cites in citations. Gather and pass on only; the skills choose and write",
"inputs": { "intent_page": "<model>/intents/<intent>.md", "gather": "<working>gather.json",
"placement": "<working>placement.json", "record": "<record>",
"split": "<working>split.json", "spine": "<model>/_spine.yaml",
"product_base": "<product_base>", "change": "<the person's change, when this is a re-run>" },
"outputs": { "slice": "<working>slice.json", "fills": "<working>fills.json",
"no_slice": "<working>no-slice.json",
"manifests": "<working>manifests/",
"grounding_report": "<working>grounding-report.json",
"citations": "<working>citations.json" },
"skills": ["author-slice", "author-domains", "enrich-capability-ice", "enrich-grounding"],
"task_id": "shape-slice-shape" }
When `no-slice.json` is written, no slice is written (C6). The run never asks for the
surface or the tester: `author-slice` proposes them from the model and memory, and the person
sees them in the one brief (Step 11). `no-slice.json` is written only when nothing in the model or
memory names a surface a person can test end to end: tell the person its reason in the run's
report and close HALTED — the model is as it was (REC6).
**SE-8 (F6/C6):** `slice.json` names exactly one surface (an object, or a list of one) with
a name equal to `vertical` and a `persona_ref`; or, when there is none, `no-slice.json` holds
the reason and no slice was written.
**SE-9 (F8/C8):** every feature in `slice.json` `feature_refs` is in the spine or in
`fills.json`; every page `fills.json` names is on disk in its folder (`persist_fills.py`
refuses a page outside it); every existing part the slice needs that lacked the intent or a
known domain is in `fills.json` `updates` and, after Step 8, in `persist-fills.json`
`updated` — no part the slice needs was skipped or left unfilled; and every spine field of an
updated part is as it was before the run, except its `intents`, a parent it had none of, and
the one-lines `persist-fills.json` `one_lines` names, each one asked for by the person's change.
**Step 7 — Reuse a slice that already serves** · Owner: play (script), then `product-os-keeper` · Depends on: Step 6
```
python3 scripts/find_serving_slice.py --spine <model>/_spine.yaml --intent <intent> \
--features <feature_refs, comma-separated> --surface "<vertical>" > <working>serving.json
```
A match (the slice lists the intent, or has the same features in the same order and the
same surface) means that slice is reused (C7): `serving.json` `reuse_args` is
`["--reuse", "<slice id>"]`. A slice in progress (its epic is past ready) is never a match:
it is in `in_progress`, the run does not touch it, and the shaped slice is written as a new
slice with its own id. On a match, `author-slice` (reuse mode) decides if the kept slice still
serves the intent as it is:
{ "task": "call author-slice in reuse mode with serving.json, the spine and the shaped slice.json; it compares the kept slice's features with the shaped slice's, pulls in each feature the intent needs, pushes out each feature that serves none of its intents now, keeps the slice testable end to end, never changes a slice in progress, and writes reuse.json {pull_in, push_out, why, decisions}. Pass it on as it is. Then read the spine lines of the matched slice through the memory tool and record the reuse decision with its trace: python3 .garura/core/tools/memory/memory.py cite --request '{...}' (question: which slice serves this business intent, and does it change; answer: from reuse.json; decided_by author-slice (via product-os-keeper); trace: those spine lines and the rule from serving.json's reason). Gather and pass on only",
"inputs": { "serving": "<working>serving.json", "spine": "<model>/_spine.yaml",
"slice": "<working>slice.json", "citations": "<working>citations.json" },
"outputs": { "reuse": "<working>reuse.json", "citations": "<working>citations.json" },
"skills": ["author-slice"],
"task_id": "shape-slice-reuse" }
**SE-10 (F7/C7):** `serving.json` exists; when its `match` is a slice id, Step 8 ran
`persist_slice.py` with `--reuse <that id>` and each `reuse.json` pull-in and push-out,
`persist-slice.json` reads `reused: true` with that id and lists `pulled_in` and
`pushed_out`, the kept slice still has one surface, a tester and one feature or more, and
after Step 8 the spine has the same number of slices as before the run; no id in
`serving.json` `in_progress` changed in the spine.
**SE-29 (F7/C7):** when `persist-slice.json` `replaced` names a part (the kept slice took its
new name, outcome or tester's action), its `differences` show each one old and new, the slice
record's last `edits` entry reads `by: a shaping run` with those parts in `changed`, and
`out_of_date` names each spine epic whose `slice_ref` is the slice; `brief.md`'s Kept slice
section has the history line and an "Out of date" line for each of those epics. Step 8 ran with
`--spine-at-start`, and `replaced` names a part only when the slice already listed the run's
intent in `spine-at-start.yaml`; the history line's `at` is a YYYY-MM-DD date.
### Phase: Persist
**Step 8 — Persist the fills and the slice** · Owner: play (scripts) · Depends on: Step 7
```
python3 scripts/persist_fills.py --root <model> --spine <model>/_spine.yaml \
--fills <working>fills.json --intent <intent> --out <working>persist-fills.json
python3 scripts/persist_slice.py --root <model> --spine <model>/_spine.yaml \
--slice <working>slice.json $(on a match, serving.json reuse_args: --reuse <id>) \
$(each reuse.json pull_in as --pull-in <id>, each push_out as --push-out <id>) \
--spine-at-start <working>spine-at-start.yaml --at <today, UTC, YYYY-MM-DD> \
--out <working>persist-slice.json
```
`persist_fills.py` adds a spine entry for each new part, marked to be deepened (status
proposed, detail directional, `metadata.deepen: true`); fills each existing part in
`updates` (the intent added, its domain or capability set when it has none, its spine
one-line replaced when the update gives one from the person's change, listed in `one_lines`
with the old and new text; every other field kept); refuses a page outside its folder; moves a capability that gets its domain, and its
features, out of the holding folder and fixes every link to them; sets `metadata.thin: true`
and the one Thin line ("Thin: proposed from the person's words; deepen with
`/discover-capabilities`.", ontology v14), just before the ICE section, for each part in `thin`
(an older thin line is replaced); and puts each new domain and capability on the intent page's Touches list
("— adds"), each filled one not listed yet ("— changes"). Its result names `written`,
`updated`, `moved`, `thin`, and the paths the guard needs: `allow`, `modify_only` and
`remove_only`. `persist_slice.py` writes the spine `slices` entry with exactly six fields —
`id`, `slug`, `feature_refs`, `vertical`, `intents` and `record` — and the record at
`<model>/slices/<id>.yaml` (slice.yaml v2). With `--reuse` it appends the intent to the
reused slice, applies each pull-in (in its place in build order) and push-out, and lists
`differences`, `upgraded_to_v2` and `dropped_fields` for the brief; it refuses a slice in
progress. Both refuse any path that leaves the model folder, and write each file through
`safe_write.atomic_write` (a temp file, then a rename). Each refuses with exit 2 and writes
nothing when a rule fails: send the message to the `shape-slice-fix` contract (Step 9), so
`author-slice` fixes `slice.json` or `fills.json`, and run it again.
**SE-11 (F9/C9):** `persist-slice.json` and `persist-fills.json` read `ok: true`; no plan,
epic, story or eval file was written by this run (no path under `.garura/project/plans/`
except the hidden file of Step 8b, and the spine's `epics` list is as it was at the run's
start).
**SE-20 (F16/C16):** each part in `fills.json` `thin` is in `persist-fills.json` `thin`; its
spine entry (the `ices` entry, or the part's own entry) has `metadata.thin: true`; its page
holds the one Thin line just before its ICE section and a goal, a limit, a "Fails if" line, a
context line and a check (Step 9 checks the marks agree); and the run did not stop or send
the person to a discovery play.
### Phase: Hidden file
**Step 8b — Write the hidden file** · Owner: play (script), then `product-os-keeper`, then play (script) · Depends on: Step 8
The slice's hidden file goes in its home plan's evals folder, made also when no plan file
exists (C15). The script lists the "Fails if" lines of each feature's ICE and its
capability's ICE, and keeps the ids of the entries already in the file:
```
python3 scripts/write_hidden_file.py lines --root <model> --spine <model>/_spine.yaml \
--slice-id <id from persist-slice.json> --plans-root <plans> --out <working>hidden-lines.json
```
{ "task": "copy standards/rules/expectation-generation.md into short-term memory through the memory tool (rule); gather the pages of the two ICE of each feature in hidden-lines.json that is not kept (ice_pages: each part's page with its ## Agent intent (ICE) section, or its ice.md, as the spine names them); call author-failure-recovery with lines, ice_pages and rule; it drafts one recovery per Fails if line — when, way_back, who_fixes exactly 'the builder can fix this alone' or 'a person must decide' — and lists vague lines. Pass its hidden-recoveries.json on as it is; never write the hidden file. Gather and pass on only",
"inputs": { "lines": "<working>hidden-lines.json", "spine": "<model>/_spine.yaml",
"product_base": "<product_base>",
"ice_pages": "<the ICE page of each feature and its capability, under <model>, from the spine>",
"rule": "{stm_base}<issue>/context/memory/<path of expectation-generation.md as found>" },
"outputs": { "recoveries": "<working>hidden-recoveries.json" },
"skills": ["author-failure-recovery"],
"task_id": "shape-slice-hidden" }
```
python3 scripts/write_hidden_file.py write --lines <working>hidden-lines.json \
--recoveries <working>hidden-recoveries.json --plans-root <plans> \
--out <working>hidden-write.json
python3 scripts/write_hidden_file.py check --root <model> --spine <model>/_spine.yaml \
--slice-id <id> --plans-root <plans> --out <working>hidden-file.json \
--lines <working>hidden-lines.json \
--clear-working <working>hidden-lines.json \
--clear-working <working>hidden-recoveries.json
```
`write` refuses with exit 2, and writes nothing, a line with no recovery or two, any other
"who fixes" text, or a path outside the plans folder: send the message to the
`shape-slice-hidden` contract, so `author-failure-recovery` drafts again (REC15). It keeps
each kept entry byte for byte, adds the new ones, and puts the count of lines too vague to
judge, by feature, in `hidden-write.json` for the brief. `check` exits 1 when a line or an
entry is missing, or when an entry `hidden-lines.json` marks kept is not as it was (`--lines`:
a kept entry changes only through an edit the person approved, C15, F20): fix it, or put the
kept entry back, and run all three again. When `check` reads ok it deletes the two
working files it is given, so no copy of the "Fails if" lines or their ids stays outside the
evals folder (C15); when it is not ok it keeps them; it refuses a path under the plans
folder. The hidden file is
`<plans>/<plan-id>/evals/<slug>/failure-and-recovery.md` (`hidden-file.json` `path`), where
`<plan-id>` is the number of the slice's first intent.
**SE-19 (F15/C15):** `hidden-file.json` reads `ok: true`; its entries equal the slice's
`feature_refs`; its line count equals the count of "Fails if" lines of the two ICE of each
feature; each line has exactly one recovery; its path is in the home plan's evals folder; and
its `cleared` names `hidden-lines.json` and `hidden-recoveries.json`, which are no longer in
`<working>`.
**SE-28 (F20/C15):** `hidden-file.json` `kept_unchanged` names every entry `hidden-lines.json`
marked kept, and its `problems` hold no "kept entry" line — a shaping run changed no kept
entry of the hidden file.
### Phase: Check
**Step 9 — Check the model** · Owner: play (script) · Depends on: Step 8b
```
python3 scripts/slice_check.py --root <model> --spine <model>/_spine.yaml \
--slice-id <id> --intent <intent> --baseline <working>lint-before-run.json \
--fills <working>fills.json --out <working>slice-check.json
```
It runs `lint_grounding.py --root <model> --spine <model>/_spine.yaml` (the structure and
link check, with the old-name guard), checks that the slice lists the intent, checks every
spine slice's entry (the six fields) and record (slice.yaml v2, with a persona_ref), and
checks that the thin marks of each part of the slice agree: a part marked thin (its spine
metadata, or `fills.json` `thin`) must have the Thin line on its ICE page, and a page with
the Thin line must have `metadata.thin: true` (any line that starts "Thin:" just before the ICE
section is the Thin line, so a page with the older line still reads thin). Any problem in the slice's own
pages (its features, their capabilities and their domains) fails it, also one the baseline
had; a new problem anywhere fails it; an older problem elsewhere is in `older_problems`.
Exit 1 (REC11): dispatch the agent to have each problem fixed by the skill that wrote what the
problem names, then re-run Steps 7–9; the run does not go on until it reads valid:
{ "task": "have each problem in slice-check.json (or a script's refusal message) fixed by the skill that wrote what it names: call author-slice in fix mode with slice.json, fills.json and the problems for each problem in slice.json or fills.json; call enrich-grounding in fix mode with the pages the problems name, the templates' copy, the intent page, the record, split.json and fills.json for each problem in a page. Each skill lists in not_mine what is not its own; send that to the other skill. Pass each output on as it is. Gather and pass on only",
"inputs": { "problems": "<working>slice-check.json", "slice": "<working>slice.json",
"fills": "<working>fills.json", "intent_page": "<model>/intents/<intent>.md",
"record": "<record>", "split": "<working>split.json",
"product_base": "<product_base>" },
"outputs": { "slice": "<working>slice.json", "fills": "<working>fills.json",
"grounding_report": "<working>grounding-report.json" },
"skills": ["author-slice", "enrich-grounding"],
"task_id": "shape-slice-fix" }
**SE-12 (F11/C11):** `slice-check.json` reads `valid: true`, written after the last write of
Steps 8 and 8b, with no problem in the slice's own pages and no new problem; its
`older_problems` are lint errors the baseline already held, in pages the slice does not use,
and each is shown in the brief.
**Step 10 — Guard the writes** · Owner: play (script) · Depends on: Step 9
```
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
--also-tree .garura/project/plans \
--allow 'product-os/_spine.yaml' --allow 'product-os/slices/<id>.yaml' \
--allow 'product-os/intents/<intent>.md' \
--allow '.garura/project/plans/<hidden-file.json path>' \
$(each path in persist-fills.json "allow", as --add-only '<path>') \
$(each path in persist-fills.json "modify_only", as --modify-only '<path>') \
$(each path in persist-fills.json "remove_only", as --remove-only '<path>') \
--out <working>guard-report.json
```
The allowed set is C9's: the spine, the slice record, the intent page (written by C1, or its
Touches list), the pages the fills added, filled or moved, and the one hidden file. Under
`.garura/project/plans/` any other change is a violation. Exit 1: re-run with `--restore` to
undo the writes outside the set (REC9), then run Steps 8–10 again.
**SE-13 (F9/C9):** `guard-report.json` reads `ok: true` — no model path and no path under
`.garura/project/plans/` outside the allowed set changed.
### Phase: Trace
**Step 10b — Record the live decisions; check their trace** · Owner: `product-os-keeper`, then play (script) · Depends on: Step 10
Each decision the slice rests on is recorded again after the run's last write, so its trace
names the lines as they now are (C10):
{ "task": "record again, after the run's last write, each decision the slice now rests on, with memory.py cite against the current lines (read through the memory tool; the exact lines, never a whole file): the features and their order, the surface, the tester (slice.json decisions), the reuse decision (reuse.json decisions), and each proposed answer the slice uses. Each question and answer is the one the skill wrote; change none. Write citations-live.json {live: [the citation ids the memory tool gives for these new records]}. Memory tool only; no skill",
"inputs": { "citations": "<working>citations.json", "slice": "<working>slice.json",
"reuse": "<working>reuse.json",
"persist_slice": "<working>persist-slice.json", "record": "<record>" },
"outputs": { "live": "<working>citations-live.json" },
"skills": [],
"task_id": "shape-slice-trace" }
```
python3 .garura/core/tools/memory/memory.py citations --issue <issue> --check \
> <working>citations-check.json
python3 scripts/trace_check.py --issue <issue> --live <working>citations-live.json \
--check <working>citations-check.json --out <working>trace-check.json
```
Exit 1 lists a live decision whose source changed or that has no record: re-record it and
run both again. The other records are `not_live` (replaced or older); they are not counted.
**SE-23 (D5/C10):** `citations-live.json` holds one decision for each of the features and
their order, the surface, the tester, the reuse, and each proposed answer the slice uses;
each was recorded after `persist-slice.json` and `hidden-file.json` were written;
`trace-check.json` reads `problems: []` and lists the other records apart as `not_live`.
### Phase: Checkpoint (class: standard, pinned)
**Step 11 — The one brief and the checkpoint** · Owner: play (+ `product-os-keeper`, with `author-slice-brief`, for the opening) · Depends on: Step 10b · Checkpoint (class: standard, **pinned** — the person decides; never skipped by config, `standards/rules/gate-config.md`)
The person gets one brief, not a list of questions (C3, C17). `author-slice-brief` writes
only its opening, by the rule for asking a person; the script builds the rest from the run's
records, so no part can be left out:
{ "task": "call author-slice-brief with kind confirmation, slice.json, the intent page, intent-page.json, brief-split.json, gather.json and asking_rule; it writes the opening of the confirmation brief: two or three plain sentences that say what the person decides (the slice that shows business intent '<title>' is served), what happens with their reply, and, when the run wrote the intent page from the issue, that it did — by the rule for asking a person, or the play's floor when gather.json has rule_missing. Pass its opening.md on as it is. Gather and pass on only",
"inputs": { "kind": "confirmation", "slice": "<working>slice.json",
"intent_page": "<model>/intents/<intent>.md",
"intent_page_record": "<working>intent-page.json",
"split": "<working>brief-split.json", "gather": "<working>gather.json",
"asking_rule": "../_shared/asking-a-person.md" },
"outputs": { "opening": "<working>opening.md" },
"skills": ["author-slice-brief"],
"task_id": "shape-slice-opening" }
```
python3 scripts/render_brief.py --working <working> --root <model> \
--spine <model>/_spine.yaml --opening <working>opening.md --out <working>brief.md
python3 scripts/record_questions.py brief --brief <working>brief.md \
--split <working>brief-split.json
```
The brief holds: what the person runs and who tests it; the features in build order under
each capability, each capability linked to its domain and each part marked new, filled or
reused; the hidden file (its place, its "Fails if" lines, how many the builder can fix alone
and how many a person must decide, the lines too vague to judge, and the line that says a
kept line changes only through `/shape-slice #<intent-issue> --edit: <words>`); what is left out; each assumption; the thin-ICE warning;
for a reuse, each difference, each feature pulled in or pushed out, each field a record
upgrade set or dropped, and, when the kept slice takes new text, its history line and each
epic of the slice named as out of date; each spine one-line a fill replaced, old and new; the model check's older problems and warnings; the count of questions
with no proposed answer and each open question with its four parts; and, when the rule for
asking a person is missing, the line that says so. `record_questions.py brief` checks the
structure only: it is a floor, and the person's read of the brief is the full test. Exit 1
lists each problem (REC3, REC17): a problem in the opening goes back to the
`shape-slice-opening` contract; a problem in a section goes back to the step whose record it
reads; then render again. The brief is never edited by hand. Frame the
brief with the approval prompt from `template-manager` (`standards/templates/approval-prompt.md`)
and show it. Ask the person to type **yes**, **change: <what>**, or **stop**, and to answer
any question by its id in the same reply; a plain yes accepts each proposed answer. Save the
reply word for word to `<working>reply.md`, then record the answers and the decision:
```
python3 scripts/record_questions.py answers --reply <working>reply.md \
--split <working>brief-split.json --out <working>answers.json
python3 scripts/record_questions.py answer --record <record> --answers <working>answers.json \
--at <now, UTC> --run <this run id>
python3 scripts/record_questions.py check --record <record> --split <working>split.json
python3 scripts/record_approval.py --decision <yes|change|stop> --reply "<their exact words>" \
--at <now, UTC> --out <working>approval.json
```
`check` exits 1 while any asked question has no answer, or a blocking question was skipped:
ask those questions again, in one message, and repeat the commands with the new reply. A
non-blocking question the person skips is recorded as deferred, in their words: its
assumption stands.
- **yes** → Step 12.
- **change** → undo this round's slice, fills, moves and hidden file, and the intent page
lines the fills changed; keep the intent page itself (Step 1 may have written it):
`scoped_write_guard.py --product-base <product_base> --base-ref HEAD --also-tree .garura/project/plans --restore --allow 'product-os/intents/<intent>.md' --out <working>guard-restore.json`, then
`persist_fills.py --undo <working>persist-fills.json --root <model> --intent <intent> --out <working>fills-undo.json`
(it takes out the lines this round added and puts back the lines it changed; every line
that was on the page before the run stays, `**Touches:** none yet` included); then re-run
Step 6 with the change, and Steps 7–11; show the brief again.
- **stop** → undo every write of the run (an EMPTY allow set):
`scoped_write_guard.py --product-base <product_base> --base-ref HEAD --also-tree .garura/project/plans --restore --out <working>guard-restore.json`;
write `git status --porcelain -- <model> .garura/project/plans` to `<working>stop-check.txt`;
close HALTED. Nothing is committed.
**SE-6 (F3/C3):** the person saw one brief; `record_questions.py brief` read `ok: true` for
the brief that was shown (the opening, every section, and each question with the four
parts); `questions-checked.json` showed no `shape_problems`; each question has two or more
examples from different domains.
**SE-21 (F3/C17):** `brief.md` has each C17 section (What you run, Who tests it, Features,
Hidden file, Left out, Assumptions, Model check, Questions; Thin ICE when a part is thin;
Kept slice on a reuse); the questions appear only inside it (and in the early brief of Step
5).
**SE-18 (F17/C17):** every line of `persist-slice.json` `differences`, `pulled_in`,
`pushed_out`, `upgraded_to_v2`, `dropped_fields`, `replaced` and `out_of_date`, every `persist-fills.json` `one_lines`
entry (old and new), every `slice-check.json` warning and
older problem, each thin part and each assumption has a plain-words line in `brief.md`.
**SE-7 (F5/C5):** `record_questions.py check` reads `ok: true` (exit 0) — every asked question
and every assumption of `split.json` is in `<record>`, every asked question has an answer
that is not empty, and each answer the person typed is on its entry in their words (a plain
yes as "accepted as proposed").
**SE-14 (F4/C4):** every assumption in `split.json` is in `brief.md`, one line each, and each
is in `<record>` as kind `assumption`.
**SE-15 (F12/C12):** `approval.json` holds the person's typed words; no commit after
`start-sha.txt` exists unless `approval.json` reads `decision: yes`; after a change, every line
of the intent page before the run is on it (`fills-undo.json` reads `ok: true`); after a stop,
`stop-check.txt` is empty and HEAD equals `start-sha.txt`.
**SE-16 (F14/C14):** the brief, the intent page, the hidden file and every page the fills
added use personas and plain words (ASD-STE100) and name no real person —
`grep -i "$(git config user.name)"` over them finds nothing.
**SE-25 (F19/C19):** every agent contract of this run has a `"skills"` list, and each name in
it is a sibling folder of the installed play, `../<name>/`, that holds a `SKILL.md` (only
`shape-slice-trace` and `shape-slice-edit-trace` have `"skills": []`);
each file an agent passed on (`intent-draft.json`, `placement.json`, `questions.json`,
`proposals.json`, `early-opening.md`, `slice.json`, `fills.json`, `no-slice.json`,
`reuse.json`, the manifests, `grounding-report.json`, `hidden-recoveries.json`,
`opening.md`, `edit-list.json`) is the output of a skill its contract names, with no change made by the agent;
the proposals reached `questions.json` only through `merge-proposals`, and the early brief's
questions only through `render_brief.py --early`.
### Phase: Commit
**Step 12 — Commit the model delta and the hidden file** · Owner: play · Depends on: Step 11 (yes)
The commit runs only when the recorded answer is yes (C12): the gate script runs right
before it, and any exit but 0 stops here — nothing is committed.
```
python3 scripts/record_approval.py verify --file <working>approval.json
git add -- <model> .garura/project/plans/<hidden-file.json path>
git commit -m "feat(model): shape slice <id> for business intent #<intent-issue> (#<issue>)"
```
### Phase: Edit (only with `--edit:`, C20)
An edit run goes from Step 0 straight here, and from here to the end sequence. Steps 1–12 do
not run. `<id>` is the slice the spine links to `<intent>`.
**Step X1 — Read the slice's records** · Owner: play (scripts) · Depends on: Step 0
Save the words after `--edit:` word for word to `<working>edit-request.md`. Take the model
check, and copy the slice's three records (the slice record, the hidden file, the spine):
```
python3 scripts/slice_check.py baseline --root <model> --spine <model>/_spine.yaml \
--out <working>lint-before-run.json
python3 scripts/edit_slice.py snapshot --root <model> --spine <model>/_spine.yaml \
--intent <intent> --plans-root <plans> --working <working> --out <working>edit-before.json
ls "$(dirname <edit-before.json files.hidden>)" > <working>edit-evals.txt
```
`snapshot` refuses (exit 2) when the spine links no slice, or more than one, to the intent:
show its message and close HALTED; nothing is written and no copy is made (it checks every
record is there before it copies one). The copies go to `<working>edit-before/`.
An edit may change a slice in progress (C7, C20): `edit-before.json` reads `in_progress: true`
and names each epic past ready, with its status, and the run goes on.
**Step X2 — Turn the person's words into the list of changes** · Owner: `product-os-keeper`, then play (script) · Depends on: Step X1
{ "task": "call author-slice in edit mode with the person's words (request), the copies of the slice's records (before), the names of the files in the slice's evals folder (evals) and, on a re-run, the last refusal (refused); it writes edit-list.json: one change for each part the words name (file, part, the old text word for word, the new text, why), each record the edit makes out of date (named, not changed; when edit-before.json reads in_progress, each epic it names), and what the words name that an edit does not change (not_editable). Pass it on as it is. Gather and pass on only",
"inputs": { "request": "<working>edit-request.md", "before": "<working>edit-before.json",
"evals": "<working>edit-evals.txt",
"refused": "<working>edit-list-check.json (on a re-run only)" },
"outputs": { "list": "<working>edit-list.json" },
"skills": ["author-slice"],
"task_id": "shape-slice-edit" }
```
python3 scripts/edit_slice.py check-list --before <working>edit-before.json \
--list <working>edit-list.json --evals <working>edit-evals.txt \
--request <working>edit-request.md --forbid-name "$(git config user.name)" \
--out <working>edit-list-check.json
```
`check-list` exits 1 for a change to a part an edit may not change, an old text that is not
what the record says now, an empty or unchanged new text, a "who fixes" that is neither of the
two sentences, one part changed twice, an out-of-date eval file that is not one of the
slice's eval files, or a real person's name in a new text (C14): send `edit-list-check.json` back to the
`shape-slice-edit` contract as `refused`, and check again. When it says the person's own words
name a real person, ask them, before any brief, to say it as a role, and write their new words
to `<working>edit-request.md`; nothing is written to a record. When the list names no change and
`not_editable` is not empty, the words ask for nothing an edit may change: show the person
each `not_editable` line in plain words, write nothing, clear the copy of the hidden file
(`write_hidden_file.py check --root <model> --spine <model>/_spine.yaml --slice-id <id> --plans-root <plans> --out <working>hidden-file.json --clear-working <working>edit-before/hidden.md`),
and close HALTED.
**SE-26 (F20/C20):** `edit-list-check.json` reads `ok: true` for the list that was shown; each
change names a file and a part of C20's set, and its old text is what the record said before
the edit; when `edit-before.json` reads `in_progress: true`, `brief.md` has the In progress
section with each epic it names, and its Out of date section names each of them (the script
adds an epic the list leaves out); when the run closed HALTED here or at Step X1,
`<working>edit-before/hidden.md` is not there.
**Step X3 — The edit brief and the checkpoint** · Owner: play (scripts) · Depends on: Step X2 · Checkpoint (class: standard, **pinned** — the person decides; never skipped by config)
The script builds the whole brief from the run's records: what the person asked, each change
in plain words (the feature's name and the failure line it is for, old and new, and why), each record made out of date (named, not changed), what an edit does not
change, and, for a slice in progress, an In progress section that says so and names its epic.
`<slice name>` is the name in the slice record's copy.
```
python3 scripts/render_brief.py --edit "<slice name>" --working <working> --out <working>brief.md
python3 scripts/record_questions.py brief --brief <working>brief.md --edit \
--before <working>edit-before.json
```
Exit 1 lists each problem: send it back to Step X2, then render again. The brief is never
edited by hand. Frame it with the approval prompt from `template-manager`
(`standards/templates/approval-prompt.md`) and show it. Ask the person to type **yes**,
**change: <what>**, or **stop**. Nothing is written to the model or the plans folder before
the yes. Save the reply word for word to `<working>reply.md`, then record it:
```
python3 scripts/record_questions.py edit --record <record> --issue <issue> \
--request <working>edit-request.md --reply <working>reply.md \
--decision <yes|change|stop> --at <now, UTC> --run <this run id>
python3 scripts/record_approval.py --decision <yes|change|stop> --reply "<their exact words>" \
--at <now, UTC> --out <working>approval.json
```
- **yes** → Step X4.
- **change** → add a line `Change: <their words>` to `<working>edit-request.md`, then run
Steps X2–X3 again; show the brief again.
- **stop** → nothing was written. Clear the copy of the hidden file and close HALTED:
`write_hidden_file.py check --root <model> --spine <model>/_spine.yaml --slice-id <id> --plans-root <plans> --out <working>hidden-file.json --clear-working <working>edit-before/hidden.md`;
write `git status --porcelain -- <model> .garura/project/plans` to `<working>stop-check.txt`.
**Step X4 — Apply the list; check before against after** · Owner: play (scripts) · Depends on: Step X3 (yes)
The gate script runs first; any exit but 0 stops here and nothing is written. `--by` is the
person's role (for example "the product owner"), never a real name (C14).
```
python3 scripts/record_approval.py verify --file <working>approval.json
python3 scripts/edit_slice.py apply --before <working>edit-before.json \
--list <working>edit-list.json --request <working>edit-request.md \
--by "<the person's role>" --at <today, UTC, YYYY-MM-DD> --out <working>edit-apply.json
python3 scripts/edit_slice.py check --before <working>edit-before.json \
--list <working>edit-list.json --request <working>edit-request.md \
--apply <working>edit-apply.json --out <working>edit-check.json
python3 scripts/slice_check.py --root <model> --spine <model>/_spine.yaml \
--slice-id <id> --intent <intent> --baseline <working>lint-before-run.json \
--out <working>slice-check.json
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
--also-tree .garura/project/plans \
--allow 'product-os/_spine.yaml' --allow 'product-os/slices/<id>.yaml' \
--allow '.garura/project/plans/<the hidden file, from edit-before.json>' \
--out <working>guard-report.json
python3 scripts/write_hidden_file.py check --root <model> --spine <model>/_spine.yaml \
--slice-id <id> --plans-root <plans> --out <working>hidden-file.json \
--clear-working <working>edit-before/hidden.md
```
`apply` writes exactly the list and the history lines (who asked, when, their words): on the
slice record always, and on the hidden file when it changes. When `edit-check.json`,
`slice-check.json`, `guard-report.json` or `hidden-file.json` is not ok, put every record back
(REC20, REC11, REC9, REC15):
```
python3 scripts/edit_slice.py restore --before <working>edit-before.json --out <working>edit-restore.json
```
then send the report that is not ok (`edit-check.json`, `slice-check.json`,
`guard-report.json` or `hidden-file.json`; it holds the problems) to Step X2 as `refused`, and
run Steps X2–X4 again: the person sees the new list before it is written, and may type stop. The hidden-file check runs last, so the copy of the hidden file
is cleared only when every check reads ok.
**SE-27 (F20/C20):** `edit-check.json` reads `ok: true`, with `changes` equal to the count of
changes in `edit-list.json`; `edit-apply.json` was written after `approval.json` read
`decision: yes`; the slice record holds an `edits:` entry with the person's words, and the
hidden file holds an "Edited on" line when the list changed it; `slice-check.json`,
`guard-report.json` and `hidden-file.json` read ok; `<working>edit-before/hidden.md` is gone;
`edit-list-check.json` ran with `--forbid-name`, and `grep -i "$(git config user.name)"` over
`edit-request.md`, the slice record and the hidden file finds nothing (C14).
**Step X5 — Trace each change to the person's words** · Owner: `product-os-keeper`, then play (script) · Depends on: Step X4
{ "task": "for each change in edit-list.json, read the edit's entry in the question record through the memory tool (memory.py read --issue <issue> --tier short-term --path <issue>/context/grounding-questions.md --start <n> --end <m>, the lines of that entry only) and record the change with memory.py cite: question '<file>, <part>: what does it say after the edit?', answer the new text, decided_by 'author-slice (edit mode, via product-os-keeper)', and a trace whose source is that read answer (tier, path, start, end, fingerprint). Write citations-live.json {live: [the citation ids]}. Memory tool only; no skill",
"inputs": { "list": "<working>edit-list.json", "record": "<record>" },
"outputs": { "live": "<working>citations-live.json" },
"skills": [],
"task_id": "shape-slice-edit-trace" }
```
python3 .garura/core/tools/memory/memory.py citations --issue <issue> --check \
> <working>citations-check.json
python3 scripts/trace_check.py --issue <issue> --live <working>citations-live.json \
--check <working>citations-check.json --out <working>trace-check.json
```
Exit 1 lists a change whose trace is missing or changed: record it again and run both again.
**Step X6 — Commit the edit** · Owner: play · Depends on: Step X5
```
python3 scripts/record_approval.py verify --file <working>approval.json
git add -- <model> .garura/project/plans/<the hidden file, from edit-before.json>
git commit -m "feat(model): edit slice <id> for business intent #<intent-issue> (#<issue>)"
```
Then the end sequence (E1–E4) runs as in a shaping run.
### Phase: End sequence (injected — D2 position: both, tail)
Each member runs as a sub-play with `parent_run_id`; each resolves its own context from the
branch and config.
**Step E1 — commit-change** · blockedBy: Step 12 (Step X6 on an edit) — commits the run records under `{stm_base}<issue>/`.
{ "play": "commit-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "<working>end/commit-change.json" } }
**Step E2 — propose-change** · blockedBy: E1
{ "play": "propose-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "<working>end/propose-change.json" } }
**Step E3 — check-change** · blockedBy: E2 — a reject stops the chain before merge.
{ "play": "check-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "<working>end/check-change.json" } }
**Step E4 — merge-change** · blockedBy: E3 — lands on the plan's branch when the change sits in a plan.
{ "play": "merge-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "<working>end/merge-change.json" } }
**SE-17 (F13/C13):** `start-change.json` shows the change opened by start-change; the
`<working>end/` results are present in order; the PR's base is the plan's branch when the
intent has a plan (else main), and `merge-change.json` reports it merged.
### Phase: Scenario Validation
**Step 13 — Scenario evals** · Owner: play · Depends on: E4
- **SCE-1 (S1 — product owner, a new slice):** `slice-check.json` reads `valid: true`,
`hidden-file.json` reads `ok: true`, `record_questions.py brief` read `ok: true` for
`brief.md`, `approval.json` reads `decision: yes`, `guard-report.json` reads `ok: true`, and
the spine holds the slice with features in build order, one `vertical` and the intent in
`intents`; the model commit comes after the yes.
- **SCE-2 (S2 — product owner, no intent page):** `<model>/intents/<intent>.md` exists and
`intent-page.json` reads `written: true` with the reason that names the issue.
- **SCE-3 (S3 — product owner, a slice already serves):** `serving.json` names the match,
`persist-slice.json` reads `action: updated` or `unchanged`, the spine has as many slices
as at the run's start, and that slice lists the intent.
- **SCE-4 (S4 — product owner, a small budget):** with tier xs and 8 gaps, 2 blocking,
`split.json` asks 5 or fewer, both blocking among them, and holds one assumption for each
gap not asked, each in `brief.md`.
- **SCE-5 (S5 — product owner, no surface):** `no-slice.json` holds the reason, and the
spine's `slices` are as at the run's start.
- **SCE-6 (S6 — product owner, stop):** `approval.json` reads `decision: stop`,
`stop-check.txt` is empty (the model tree and the plans tree), and HEAD equals
`start-sha.txt`.
- **SCE-7 (S7 — product owner, a part with no ICE):** the part is in `persist-fills.json`
`thin`, `slice-check.json` lists no thin-mark problem, its `hidden-file.json` entry lists the
thin ICE, and `brief.md` has the Thin ICE section with `/discover-capabilities`.
- **SCE-8 (S8 — product owner, an older model):** `slice-check.json` reads `valid: true`, and
its `older_problems` are errors of `lint-before-run.json` in pages the slice does not use,
each in `brief.md`.
- **SCE-9 (S9 — product owner, an existing capability):** `persist-fills.json` `updated` names
the capability and `moved` names its page and its features' pages; its spine entry lists the
intent and the domain; `slice-check.json` names no orphan page; `brief.md` marks it filled.
- **SCE-10 (S10 — product owner, no plan file):** with no plan file and three features,
`hidden-file.json` reads `ok: true` with three entries, its path is
`<plan-id>/evals/<slug>/failure-and-recovery.md`, and every line has one recovery.
- **SCE-11 (S11 — product owner, an edit):** after `--edit` asking for two "who fixes" lines
to go to a person and a yes, `edit-check.json` reads `ok: true` with `changes: 2`,
`hidden-file.json` reads `ok: true`, the hidden file holds two more "a person must decide"
lines and an "Edited on" line with the person's words, the slice record holds the `edits:`
entry, and `git diff <start-sha> -- <model> .garura/project/plans` shows no other line.
- **SCE-12 (S12 — product owner, a kept slice shaped again):** with a kept slice that has an
epic, shaped again with a new name and the same surface and tester, `persist-slice.json`
`replaced` names `slice.name` and `out_of_date` names the epic, the slice record's last
`edits` entry names `slice.name`, and `brief.md` shows the name old and new, the history line
and the epic as out of date.
On an edit run, only SCE-11 applies; the others are for a shaping run.
### Phase: Evidence & Close
**Step 14 — Close** · Owner: play · Depends on: Step 13
Run the Standard Play Close. The close is stop-condition gated: the baked
`stop-condition.yaml` (D1 the slice check reads valid; D2 the approval reads yes; D3 the
write guard reads ok; D4 the question record exists; D5 the trace check of the live
decisions names no changed source; D6 the hidden-file check reads ok) must read **held**
before a COMPLETED close. A stop, or a run with no slice, closes HALTED.
```bash
# --- Standard Play Close (shared; see ../_shared/play-close.md) ---
# Run the close in ../_shared/play-close.md. Its Step L loads the templates through
# template-manager and sets ts and evidence_dest; then run this play's own lines below;
# then Steps C0-C2.
# evidence_base, slug (project-scoped play):
# evidence_base="${stm_base}${issue}/evidence/shape-slice/" ; slug="#${issue}"
# Session identity stamp (#463) — close phase; start phase ran at pre-flight
session_stamp=$(python3 scripts/session_stamp.py --phase close \
--marker "${stm_base}${issue}/status/session-stamp-shape-slice.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Evaluate the Done means against the issue folder.
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}${issue}/" \
--out "${stm_base}${issue}/status/stop-condition-shape-slice.yaml"
sc_exit=$? # 0 held · 1 unmet · 2 error
```
**Step C0 — bind the verdict.** `sc_exit == 0` (held) permits `status: COMPLETED`. Anything
else closes `HALTED` with `exit_reason: stop_condition_unmet`, and the evidence's Stop
Condition section names every unmet clause. An unevaluable verdict is never a pass.
**Step C1 — Write evidence file.** Gated by the resolved `evidence.record` flag (per-play
`evidence.plays.shape-slice`, else global; absent ⇒ record). When false, skip the write and
record `evidence skipped (record=false)` in the report's pointer line. Otherwise fill the
`evidence-file.md` slots (play `shape-slice`, run_id `shape-slice-${ts}`, issue, the intent
issue, started_at/completed_at, status per C0, exit_reason; artifacts produced: the intent
page (and whether this run wrote it), `lint-before-run.json`, `questions-checked.json`,
`split.json`, the question record, `slice.json`, `fills.json`, `serving.json`, `reuse.json`,
`persist-fills.json`, `persist-slice.json`, `hidden-file.json` and the hidden file's path,
`slice-check.json`, `guard-report.json`, `trace-check.json`, `brief.md`, `approval.json`, the
model commit sha, and the skills each agent contract named (on an edit run: `edit-request.md`,
`edit-before.json`, `edit-list.json`, `edit-list-check.json`, `edit-apply.json`,
`edit-check.json`, `hidden-file.json`, `slice-check.json`, `guard-report.json`,
`trace-check.json`, `brief.md`, `approval.json`); step and scenario eval results SE-1…SE-29 /
SCE-1…SCE-12 (an edit run reports SE-26, SE-27 and SCE-11, a shaping run the others); the checkpoint
answers in the person's words; the session identity stamp fields from $session_stamp (#463);
and stop_condition per C0) and write to `$evidence_dest`. Do NOT hand-author the body.
**Step C2 — Render delivery report.** Fill the `delivery-report.md` slots and output the
report: `## shape-slice Delivered — #${issue}`, the Run Summary table, the Pipeline Steps
table from the task DAG, the Artifacts Produced table (the slice and its features, its
surface and tester, the intents it serves, the parts added or filled in the model to be
deepened, the thin ICE, the hidden file, the intent page when this run wrote it, the count
of questions with no proposed answer, the PR), and the **Next** line from the Next-line map
(`$next_map`, from `template-manager`), per `play-close.md`; omit it when the map's command
is null. Next Steps: run `/shape-plan <intent-issue>` to make the plan, the epic and the stories
for this slice; when the intent already has a plan, run `/update-plan <intent-issue>` to add this
slice's epic and stories to it. End with a
pointer to `$evidence_dest`. Always emitted; never gated.
```bash
# --- end Standard Play Close ---
```
## Scenario Validation
| Scenario | Persona | Eval |
|----------|---------|------|
| S1 — a new slice | product owner | SCE-1 |
| S2 — no intent page | product owner | SCE-2 |
| S3 — a slice already serves | product owner | SCE-3 |
| S4 — a small budget | product owner | SCE-4 |
| S5 — no surface | product owner | SCE-5 |
| S6 — stop at the checkpoint | product owner | SCE-6 |
| S7 — a part with no ICE | product owner | SCE-7 |
| S8 — an older model | product owner | SCE-8 |
| S9 — an existing capability | product owner | SCE-9 |
| S10 — no plan file | product owner | SCE-10 |
| S11 — an edit | product owner | SCE-11 |
| S12 — a kept slice shaped again | product owner | SCE-12 |
## Recovery
| For | Trigger | Direction | Handoff |
|-----|---------|-----------|---------|
| F1 | the run worked from the issue text alone, or stopped because the model had no page for the intent | read the intent from the model; write the missing page from the issue, then go on | autonomous |
| F2 | a question shown is one the model or memory already answers | drop the question, and use the answer the model or memory gives | autonomous |
| F3 | the person got a list of questions in place of one brief, the run asked first about a gap it could answer (the surface), or a question broke the rule for asking a person | put the questions in the one brief, propose an answer for each gap the run can answer, rewrite each question to the rule, and show the brief again | autonomous |
| F4 | more questions than the tier allows beyond the blocking ones, a gap open with no written assumption, or an assumption the person did not see | cut the questions to the budget, blocking first, and write an assumption for every gap not asked; show the assumptions in the brief | autonomous |
| F5 | a question or answer is not in the record, or an answered question was asked again | record every question and answer, and take back any repeat question | autonomous |
| F6 | a slice has no tester, no surface, or more than one surface | take the slice back; propose one surface and its tester from the model and memory, and show them in the one brief — never ask for them; when nothing names a surface a person can test end to end, write no slice, say why, and close | autonomous |
| F7 | a new slice was made for an intent a slice not in progress already serves, a shaping run changed a slice in progress, a reused slice can no longer be tested end to end, or a kept slice's name, outcome or tester's action changed without the brief showing it old and new, without a history line, or without the brief naming its epic as out of date | remove the new slice and add the intent to the slice that serves it; put back a slice in progress and shape a new slice; put back the feature whose change broke the end-to-end test; or, for a kept slice's text, add the missing history line, show each change old and new, name the epic as out of date, and ask again | autonomous |
| F8 | a part the slice needs was skipped or left unfilled, a page is outside its parent's folder, a moved or used page still breaks a current rule, or a fill changed a field of an existing part other than its intents, its missing parent, or the one-line the person's change asked for | add or fill the part at the depth known, at its folder, marked to be deepened, and bring each moved or used page up to the current rules; put back any other field a fill changed | autonomous |
| F9 | the run wrote outside the allowed set (a plan, an epic, a story, an eval, a file under the plans folder other than the hidden file, or another file) | undo the write outside the allowed set (`scoped_write_guard.py --also-tree .garura/project/plans --restore`) | autonomous |
| F10 | a proposed answer has no source the run can name, a trace names a source not read through the memory tool, or the count of questions with no proposed answer is missing or wrong | strip the proposed answer that has no source — never replace it with another — read the source through the memory tool before citing it, and report the count | autonomous |
| F11 | a problem in the slice's own pages, or a new problem, was left and the run went on; or the run stopped on an older problem in a page it does not move or use | fix each problem in the slice's own pages and each new problem, and run the check again; show an older problem elsewhere as a warning, not a stop | autonomous |
| F12 | something was committed without the person's yes, a stop left writes behind, or an undo removed a line that was on the page before the run | take the commit back, undo the writes a stop left, or put back the line the undo removed | human |
| F13 | the change landed outside the change plays, or on the wrong branch | land the change through the change plays, on the branch the target script gives | human |
| F14 | a page names a real person, or a person cannot follow it | rewrite with personas and plain words | autonomous |
| F15 | the hidden file is missing, outside the home plan's evals folder, has no entry for a feature, leaves out a "Fails if" line, or gives a line no recovery or more than one; or a working copy of its lines or ids stays outside the evals folder after it reads ok | write the missing entry or line from the two ICE, with one recovery each, in the home plan's evals folder; delete the working copies once it reads ok (`write_hidden_file.py check --clear-working`) | autonomous |
| F16 | a part the slice needs has no ICE, a thin ICE lacks a minimum line, only one thin mark is set, the brief has no thin-ICE warning, or the run stopped or sent the person to a discovery play | write the missing lines or the missing mark, and add the warning; go on | autonomous |
| F17 | the brief hid a result of the run (a reuse difference, a feature pulled in or pushed out, a field an upgrade set or dropped, a replaced spine one-line, a warning, an older problem, a thin ICE, an assumption) | show the hidden result in the brief, in plain words, and ask again | autonomous |
| F18 | the run read the rule for asking a person from a place other than `_shared/asking-a-person.md` (through the memory tool, or from another path), stopped because it was missing, or went on without saying it was missing | drop the copy read from another place and read `_shared/asking-a-person.md`; when it is missing, say so in the brief, use the play's floor, and go on | autonomous |
| F19 | an agent dispatch asked the agent to do work no named skill does (choose, draft, decide or write content, or reshape a skill's output), or a dispatch named no skills | take back what the agent made and send the work to the named skill that does it, through the agent; when no skill does it, close HALTED and name the missing skill — the agent never does it | autonomous |
| F20 | an edit changed something that is not on the list the person saw, wrote before the person's yes, changed a part an edit may not change, or left no history line; an edit of a slice in progress whose brief did not say it is in progress; or a kept entry of the hidden file changed with no edit the person approved | put back each change that is not on the approved list, or every write made before the yes (`edit_slice.py restore`), and add the missing history line; when the slice is in progress and the brief did not say so, put back the edit, show it in the brief and ask again; nothing of the edit is kept without the person's yes | autonomous |
## Pause and Resume
Steps run top to bottom. On entry, resolve the change issue from start-change's result,
check the status marker, skip completed steps, reset an in-progress step to pending, and
continue. The question record is never asked twice: a resume reads `<record>` and asks only
what is still open. A resume after the slice was written re-enters its own uncommitted
delta in the model tree and the plans tree (expected, not a dirty-tree halt). A run that
closed HALTED at a stop has nothing to resume: its writes were undone. An edit run resumes on
its own DAG (X1–X6): `<working>edit-before.json` and its copies are kept until the hidden-file
check reads ok, so a resume after X4 can still put every record back.
## Compilation Metadata
| Field | Value |
|-------|-------|
| fingerprint | sha256:b551dab40893d6adcbcd330b384a3e0f3bdfc59b7311a9018b7cdf0dd2694f9b (of `reference/ice.md`) |
| compiled_by | create-play (#795); edit-play (#842, #908, #798) — the one brief, the hidden file, the thin ICE, fills of existing parts in their folders, the own-pages model check, the live-decision trace check, the slice-in-use and slice-in-progress rules, and the agent that only gathers context while a named skill does each piece of work (C19); the edit path, which changes only what the person named, after their yes, with a history line (C20); a re-run's change to a kept slice's text, with a history line and its epic named as out of date (C7) |
| pipeline_position | both (start-change head; commit → propose → review → merge close) |
| workflow_structure | A (an early ask only for a gap no one but the person can answer; one brief at one pinned typed checkpoint; write-then-review; stop-condition gated close) |
| stop_condition | stop-condition.yaml (D1–D6), gate live at Step C0 |
| domain_agents | 1 (product-os-keeper) |
| utility_agents | 1 (template-manager — serves the approval prompt and the close) |
| skills_used | author-business-intent (one-intent mode), find-work-placement, find-slice-gaps, propose-answers, author-slice-brief, author-slice (#838), author-domains (profile: false), enrich-capability-ice (depth: directional), enrich-grounding, author-failure-recovery (author-slice, find-slice-gaps, author-slice-brief, enrich-grounding and author-failure-recovery built for this play, #842) |
| skills_missing | none |
| scripts | preflight.py, session_stamp.py, check_stop_condition.py, platform_adapter.py, scoped_write_guard.py, lint_grounding.py, model_names.py, intent_touches.py (copied unchanged from create-play references); grill_budget.py (#817); write_intent_page.py, record_questions.py, find_serving_slice.py, persist_fills.py, persist_slice.py, spine_text.py, write_hidden_file.py, slice_check.py, trace_check.py, render_brief.py, record_approval.py, edit_slice.py, safe_write.py (this play) |
| tests | test_grill_budget.py, test_find_serving_slice.py, test_persist_slice.py, test_shape_slice_scripts.py, test_write_hidden_file.py, test_render_brief.py, test_trace_check.py, test_edit_slice.py |
| step_evals | 29 (SE-1…SE-29) |
| scenario_evals | 12 (SCE-1…SCE-12) |
| recovery_entries | 20 (one per failure condition; 18 autonomous / 2 human) |