| name | understand |
| position | none |
| description | Detail one capability that /vision seeded — promote its grounding from directional to detailed, create and detail its functionalities, set the capability's own NFR + compliance needs, and roll those needs up into the product profile, firming the box and recording a decision for any out-of-box move. The product-manager step of the ProductOS strategy pipeline, after /vision and before /shape — the last detailing step. Opens no delivery issue. |
| user-invocable | true |
understand
Take a capability that /vision seeded — named, directional, no functionalities — and
detail it. /understand is the product-manager step, the last detailing step:
promote the capability's capability.md from the directional stage to the detailed stage,
create its functionalities (a spine entry plus a detailed functionality.md each), set
the capability's own concrete NFR + compliance needs, then roll those per-capability needs
into the product profile. On a still-directional box the roll-up establishes the box and
firms it to set; on an already-set box, a need that exceeds the committed ceiling halts
for a human decision that moves the box and records why. One capability per run.
Pipeline position: none. /understand 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, #498, ADR 026)
Write discipline (ADR 026, standards/rules/direct-model-write.md). The LLM enrichment
skill writes ONLY the per-node docs (capability.md, functionality.md) straight to the
live model; every shared-file mutation (the spine _spine.yaml, the profile, the box-move
decisions) is done by the deterministic keyed persist script, in place, keyed to the target
capability so it cannot touch a sibling node inside a shared file. The model tree is asserted
clean at entry (F13) and the play commits its own model delta at close (C12), 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 understand 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; #498 — migrated to
direct-model-write per ADR 026 and standards/rules/direct-model-write.md). Intent defines
constraints (C1–C12) and failure conditions (F1–F13); the expectation defines success
scenarios (S1–S5), 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 — grounded detailing and functionality authoring — to the product-os-keeper
agent via a JSON contract over files on disk, and you run the mechanical work (the shape
linter, the content-quality eval, the monotonic-up roll-up, the keyed in-place persist, and
the post-write scoped guard) through bundled scripts and an isolated judge. You never write
the grounding docs or the spine yourself, you never lower a box dimension, and you never
persist the shared files before the single checkpoint (C9) resolves — a typed approval, a
recorded config skip, or a recorded policy auto-pass — which also carries approval of any
out-of-box box-move (C8).
Forbidden: hand-writing grounding docs or spine entries; touching any capability,
functionality, or domain other than the target and its new functionalities (C10);
prioritizing — writing a slice or an epic, or flipping the capability to active (C2, that
is /shape); persisting the shared files (spine/profile/decisions) by any route other than
scripts/persist_understand.py; persisting before the checkpoint resolves; running against
a dirty product-os tree (C12/F13); closing COMPLETED without the stop-condition verdict
reading held (C11/F11).
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
product-os-keeper | Recover the capability's KB shelf (re-route), then detail the capability (directional → detailed) writing the docs in place on the live model, author its functionalities, and set its NFR + compliance needs (into the manifest) grounded in that shelf | search-kb, enrich-capability-ice | Enrich |
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).
The content-quality judge always runs as an isolated, clean-context sub-agent (optionally
on a configured different model) — never the orchestrator's own context.
Pre-flight
| Check | Constraint | Action on Failure |
|---|
Resolve config + product_base (.garura/core/config.yaml) | — | Hard halt |
Resolve grounding-eval.judge (optional model override) | C4 | Default: sub-agent on the session model |
Target capability exists in the spine and is a directional seed (detail: directional) | C1 | Hard halt (REC1) |
Clean model tree — git status --porcelain -- <product_base>product-os is empty | C12/F13 | Hard halt (REC13) |
Clean-tree assertion (C12/F13, 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 (REC13)"; 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 /understand proceeds.
Resolve the pre-flight facts mechanically with the bundled resolver — do not derive
them by inference. /understand has no branch or issue (position none):
python3 scripts/preflight.py --play understand --config .garura/core/config.yaml
It returns one JSON object of facts. /understand reads product_base (the live model —
the spine _spine.yaml and the grounding docs), stm_base (working artifacts + evidence), the
resolved grounding-eval.judge config, and evidence_record (the D1 gate). The target
capability is a runtime input (the play argument, e.g. /understand checkout); the play
resolves its entry in _spine.yaml. If the capability is absent or is not a directional
seed, hard halt (C1/REC1) — /understand details a seeded capability, it never seeds.
The run's working root (<working> below) is {stm_base}_shaping/understand/<capability>/
— the routing, the enrich manifest (enrich-manifest.yaml), the proposed profile, the
roll-up report, the change-shape, the persist record (persist-manifest.json), and the
captured scoped-guard report (guard-report.json) all live under it. These are STM,
non-model artifacts (ADR 008/017) — the model itself 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/understand/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/understand/status/session-stamp-understand.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}_shaping/understand/status/<capability>.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 docs AND the keyed persist's
shared-file mutations — 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] Enrich (detail + functionalities, docs to live) blockedBy: []
[T2] Validate the live docs blockedBy: [T1]
[T3] Roll up the box blockedBy: [T1]
[T4] Persist (keyed, in place — shared files) blockedBy: [T2, T3]
[T5] Guard the full delta + classify the shape blockedBy: [T4]
[T6] Checkpoint (approval over the full git diff) blockedBy: [T5]
[T7] Commit the model delta blockedBy: [T6]
[T8] Scenario Validation blockedBy: [T7]
[T9] Close blockedBy: [T8]
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: Enrich
Step 1 — Enrich (detail + functionalities, docs to live) · Owner: product-os-keeper · Depends on: pre-flight
The persisted model does not record which KB shelf a capability came from (/vision
kept that only in STM). So the agent first re-routes the capability to its KB shelf
with search-kb, then invokes enrich-capability-ice to detail it — promote the
capability.md to the detailed stage, author a functionality.md per functionality it
identifies, set the capability's nfr_needs + compliance_needs, and emit the implied
per-dimension levels the roll-up consumes. Per ADR 026 the skill writes the per-node docs
straight to the live model and emits the spine-delta as structured data in the manifest
(it never writes _spine.yaml, profile.yaml, or a decision):
{
"task": "re-route this capability to its KB shelf, then detail it (directional->detailed) writing capability.md + functionality.md docs in place on the live model, set its nfr_needs; emit implied NFR levels + the spine-delta into the manifest",
"inputs": { "capability": "<id + slug + path to its directional capability.md>",
"product_base": "<product_base>",
"manifest_path": "<working>/enrich-manifest.yaml" },
"outputs": { "routing": "<working>/routing.yaml",
"enrich_manifest": "<working>/enrich-manifest.yaml" }
}
The agent passes the shelf search-kb returned to enrich-capability-ice as its
kb_domain, so the grounding is the shelf the router actually resolved. It returns the
contract with the output paths on disk — never inline content. The skill writes the target's
detailed capability.md (over the live directional one) and the new functionality.md docs
IN PLACE under <product_base>product-os/, and writes enrich-manifest.yaml under
<working> (STM) with the spine-delta as structured data. It writes NO shared model file.
SE-1 (F1/C1): the target capability was resolved and is a directional seed — an absent
or non-seed capability halted the run at pre-flight (REC1).
SE-2 (C6): the enrich-manifest.yaml records grounded_in, and it matches the shelf
search-kb routed to in routing.yaml — the detail is grounded in the recovered shelf.
SE-14 (F13/C12): 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 (REC13), so the change-shape and the scoped guard reflect
only this run's delta.
Phase: Validate + Roll up
Step 2 — Validate the live docs · Owner: play · Depends on: Step 1
Run the two guards over the LIVE model before the checkpoint — shape first, then content.
Under direct-model-write the docs are already written in place, and the spine has NOT yet
been mutated for this run (the keyed persist runs after approval). So the linter runs over
the live tree, and the newly written docs are linted; full spine↔doc consistency for the
new functionality entries is completed after the keyed persist (Step 6) writes those entries.
python3 scripts/lint_grounding.py --root <product_base>/product-os
Then run the content-quality eval over the grounding docs this run wrote or changed
(the detailed capability.md and every new functionality.md, at their live paths) as a
concurrent read-only fan-out (standards/rules/concurrent-fanout.md): dispatch one
isolated, clean-context judge sub-agent PER doc, ALL IN ONE CONCURRENT BATCH — each handed
the judge prompt (standards/rules/grounding-eval.md), its doc, and the kind's per-section
guidance (it sees neither the brief nor the author's reasoning), on the model from
grounding-eval.judge.model. The three safety conditions hold: each judge only READS its
own doc, writes only its own verdict JSON, and no judge depends on another. Join —
wait for every verdict to return before gating any. Then gate every returned verdict
(order-stable, so the outcome is identical to a serial run):
python3 scripts/grounding_gate.py --verdict <verdict.json>
SE-3 (F2/C3): lint_grounding.py exits 0 over the live docs this run wrote — the
detailed capability.md conforms to the capability template's detailed stage and every
new functionality.md conforms to the functionality template.
SE-4 (F3/C4): the content-quality eval gate (grounding_gate.py) passes for EVERY
grounding doc — each section self-explains at product-manager altitude and clears the
stranger test.
SE-5 (F4/C2): the manifest promotes the capability (enrich.capability.detail: detailed)
and every functionality it names carries both a manifest functionalities entry and a live
functionality.md doc (the keyed persist in Step 6 writes the entries; the docs are on disk
now). No functionality is created without both.
SE-6 (F5/C5): every nfr_needs.<dimension> in the manifest's capability block is
concrete — a level, a measurable target, and a gate — and the needs sit on the
capability, not globally.
On any GAP, apply the matching recovery (REC2–REC6) and re-run before the checkpoint — a
content-eval fail (SE-4) is REC3: rewrite the doc to the judge's cited fixes and re-judge.
Step 3 — Roll up the box · Owner: play · Depends on: Step 1
Roll the implied levels into the profile, monotonic-up. The script reads the box from the
live spine and the manifest (both non-draft) and branches on state: a directional box is
established and firmed to set (no moves); a set box yields an out-of-box move for any
dimension or compliance regime the need exceeds:
python3 scripts/rollup_box.py --spine <product_base>/product-os/_spine.yaml \
--enrich-manifest <working>/enrich-manifest.yaml \
--out-profile <working>/proposed-profile.yaml \
--out-report <working>/rollup.json
SE-7 (F6/C7): the roll-up report shows state_after: set and never lowers a dimension
(each proposed level is the max of the current box and the need). establishing: true on a
directional box yields no box-moves.
Note — partial gates are expected. On the first firm-up, dimensions no capability has
touched yet keep their directional level; the box fills in as more capabilities are
understood. This is incremental firm-up, not a defect.
Phase: Persist (write the full delta first, ADR 026 write-then-review)
Step 4 — Persist (keyed, in place) · Owner: play · Depends on: Steps 2–3
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 docs
are already on the live model (Step 1). persist_understand.py now writes the SHARED files
in place, keyed to --capability-ref: it reads the manifest + roll-up output and flips the
target capability's spine entry to detail: detailed, sets its nfr_needs/compliance_needs,
adds its new functionality entries, writes the rolled-up profile, and writes one decision per
box-move — and it REFUSES to touch any other capability or a functionality not under the
target (this is the node-level containment the file-level guard cannot provide). No draft, no
doc copy. Nothing is COMMITTED yet — the commit (Step 7) happens only after the gate approves;
on cancel the whole delta is reverted (Step 6):
python3 scripts/persist_understand.py --enrich-manifest <working>/enrich-manifest.yaml \
--product-base <product_base> --proposed-profile <working>/proposed-profile.yaml \
--rollup-report <working>/rollup.json \
--capability-ref <capability id> --decided-by /understand --date "$(date -u +%Y-%m-%d)" \
--out-manifest <working>/persist-manifest.json
SE-9 (F8): there is one product-level decision per box-move, each carrying its
dimension, its from→to, and status accepted (in persist-manifest.json box_moves +
the written decision records).
SE-11 (F10): no slice and no epic was written, and the capability's status was not
flipped to active — prioritization is /shape's.
Phase: Guard + Classify (over the full delta)
Step 5 — Guard the full delta + classify the shape · Owner: play · Depends on: Step 4
The run's write scope (the per-play guard policy, ADR 026). The old
apply_understand.py --capability-ref encoded understand's write scope by construction;
under direct-model-write that same scope is the scoped_write_guard.py policy — resolve
<cap-dir> as the directory of the target capability's doc (e.g.
ecommerce/checkout from ecommerce/checkout/capability.md):
--allow 'product-os/_spine.yaml' # the shared spine + profile block (persist writes it)
--allow 'product-os/<cap-dir>/capability.md' # the target's detailed doc (overwrite)
--allow 'product-os/decisions/*' # box-move decisions (persist writes them)
--add-only 'product-os/<cap-dir>/*/functionality.md' # new functionality docs (added, not modified)
Guard ONCE over the full delta (C10). After ALL writes (the LLM docs from Step 1 and the
keyed persist's shared-file writes from Step 4), run the scoped guard a single time over the
whole delta. Capture its report — its ok field is the stop condition's D3 input (this
replaces the old check_apply.py verify). Then re-run the linter over the merged live spine
to complete spine↔doc consistency:
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
--allow 'product-os/_spine.yaml' --allow 'product-os/<cap-dir>/capability.md' \
--allow 'product-os/decisions/*' --add-only 'product-os/<cap-dir>/*/functionality.md' \
--out <working>/guard-report.json
python3 scripts/lint_grounding.py --root <product_base>/product-os \
--spine <product_base>/product-os/_spine.yaml
If the guard exits non-zero, re-run with --restore to revert the offending paths, apply
REC9 (a capability/functionality/domain other than the target changed), and re-persist before
the checkpoint.
Classify the full working-tree delta (C9). Classify the model tree's diff vs HEAD — now
the FULL delta (docs + spine entries + profile bars + decisions), per ADR 026 write-then-review
(no draft dir), so a high-impact run (a two-dimension box-move) yields a materially different
shape key from a prose edit:
python3 scripts/classify_change.py --play understand \
--product-base <product_base> --base-ref HEAD --out <working>/shape.json
SE-10 (F9/C10): the scoped-write guard report reads ok: true — the model delta is
confined to the target capability (its entry + doc), its new functionalities, the profile,
and decisions; no other capability, functionality, or domain changed.
Phase: Checkpoint (conditional gate, C9)
Step 6 — Human review (class: standard, conditional) · Owner: play · Depends on: Step 5
This checkpoint is a conditional gate (#467) per standards/rules/gate-config.md —
/understand is one of the eleven conditional document plays. Resolve it first match
wins: pinned (n/a here) → gates.plays.understand → the learned policy →
gates.classes.standard → gates.default (absent ⇒ on). For the policy lookup, use the
shape key classified in Step 5.
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 5 stand with no blocking finding (a lint_grounding.py gap, a
grounding_gate.py content-eval fail, or a guard violation). On auto-pass, do NOT wait: record
gate auto-passed by learned policy (shape: <shape-key>, policy v<version>) as a
Checkpoint Decisions row, include the working-tree diff summary in the run record, append
the crossing's live-eval ledger line, and proceed to Step 7 (commit):
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play understand \
--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.understand: off instead
records gate skipped by config (<resolution path>) as a Checkpoint Decisions row and
proceeds — box-move approvals ride this same gate, so the recorded skip or auto-pass
stands in evidence for them too). When on, present the detailed capability, its
functionalities, the per-capability NFR needs, and the profile changes inline over the real
model git diff — for each box-move in rollup.json, render its own explicit line item
(the dimension, the from→to levels, and the product-level decision (ADR) it will create),
distinct from the grounding approval — render the approval prompt
(standards/templates/approval-prompt.md) and wait for the typed response. Approve →
continue to Step 7 (commit). Cancel → revert the working tree (ADR 026 step 6): the full
delta is already on disk, so run the guard with --restore and an EMPTY allow set to
git restore the modified model paths and git clean/remove the new ones (byte-clean back
to HEAD), then halt — nothing was committed, and cancel means "revert what was written":
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
--restore --out <working>/guard-report.json # empty --allow ⇒ every model path reverted
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 understand \
--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-8 (F7/C8/C9): when the box is set and out_of_box: true, each move was written to
the model by the Step 4 keyed persist but is COMMITTED (made durable) only here on approval —
every box-move appears as its own line item, so an out-of-box move is never COMMITTED without
an explicit, un-rubber-stamped approval (or, when the gate resolves off by config or
auto-passes by learned policy, without the recorded skip/auto-pass row standing in evidence
for it); on cancel the whole delta — box-move decisions included — is reverted before the
commit, so nothing out-of-box becomes durable.
SE-13 (F12): 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 7 — Commit the model delta · Owner: play · Depends on: Step 6
The gate approved (or auto-passed / was skipped by config). Commit the full model delta on
the branch (C12, ADR 026 step 7) — a lightweight persist step that makes the writes durable
and advances HEAD; it is NOT the pipeline start/end sequence (understand is a middle play).
A cancelled checkpoint never reaches this step — its tree was already restored in Step 6:
git add -- <product_base>product-os
git commit -m "feat(model): detail <capability> — capability, functionalities, profile roll-up (#498)"
SE-12 (F11/C11): 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 changed.profile: true; D3 the captured guard-report.json reads ok: true) must
read held before any COMPLETED close, and the model delta is committed (C12); a run whose
persist or guard did not land closes HALTED, never COMPLETED (REC11).
Phase: Scenario Validation
Step 8 — Scenario evals · Owner: play · Depends on: Step 7
- SCE-1 (S1 — product manager, detail + functionalities): the capability is detailed
(its
capability.md promoted, its entry detail: detailed, with nfr_needs) and its
functionalities are created (each a spine entry + a functionality.md), all clean on
lint_grounding.py and passing the content-eval gate, and the stop-condition verdict
reads held.
- SCE-2 (S2 — product strategist, first firm-up): on a
directional box, the roll-up
established and firmed it to set with no decision written this run.
- SCE-3 (S3 — architect, out-of-box move): on a
set box exceeded on a dimension, the
run halted at the checkpoint surfacing the box-move as its own line item, and on approval
the box-move was committed with a product-level accepted decision recording the from→to
(written by the keyed persist, made durable only by the Step 7 commit; reverted on cancel).
- SCE-4 (S4 — product owner, non-destructive): the scoped-guard report reads
ok, so the
working-tree model delta is confined to the target capability, its new functionalities, the
profile, and decisions, and no dimension was lowered.
- SCE-5 (S5 — reviewer, the checkpoint): the detailed capability, its functionalities,
the NFR needs, and each box-move (dimension, from→to, ADR) appeared inline over the full
written delta, and no SHARED model change was COMMITTED before approval — the full delta
written in place (docs + spine/profile/decisions) shows as the branch diff and is reverted
byte-clean on cancel (ADR 026 step 6) — 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.
Phase: Evidence & Close
Step 9 — Close · Owner: play · Depends on: Step 8
Run the Standard Play Close. /understand 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/understand/" ; slug="#${issue}"
# product-scoped play : evidence_base="${product_base}_evidence/understand/" ; 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/understand/status/session-stamp-understand.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/understand/<capability>/" \
--out "${stm_base}_shaping/understand/status/stop-condition-understand.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
/understand is product-scoped: evidence_base="${product_base}_evidence/understand/"
and slug="${product_slug}" (the capability's slug, e.g. checkout).
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 REC11 (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.understand; 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 understand, run_id understand-${ts},
product_slug, started_at/completed_at, status per C0, exit_reason; artifacts produced: the
detailed capability.md, the new functionality.md docs, the spine entries written, the
firmed profile, any decision records, the persist manifest (persist-manifest.json), the
captured guard-report.json, the model-delta commit sha, the stop-condition verdict; the
content-eval verdicts; step and scenario eval results
SE-1…SE-14 / SCE-1…SCE-5; checkpoint decision from Step 4 including each approved box-move
(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: ## understand Delivered — ${product_slug}, the Run Summary table, the Pipeline
Steps table from the task DAG, the Artifacts Produced table (the detailed capability, its
functionalities, the firmed profile, any decisions), Next Steps (run /shape to prioritize
the detailed functionalities into end-to-end verticals and epics), and a pointer to
$evidence_dest. Always emitted; never gated.
# --- end Standard Play Close ---
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — detail + functionalities | product manager | SCE-1 |
| S2 — first firm-up | product strategist | SCE-2 |
| S3 — out-of-box move | architect | SCE-3 |
| S4 — non-destructive | product owner | SCE-4 |
| S5 — the checkpoint | reviewer | SCE-5 |
Recovery
| For | Trigger | Direction | Handoff |
|---|
| F1 | the target capability is absent or not a directional seed | halt and ask for a valid seeded capability (run /vision first) before proceeding | human |
| F2 | a grounding doc fails its template/shape, or a spine entry fails the schema or spine↔doc consistency | re-emit the failing doc or spine entry to conform and restore consistency before the checkpoint | autonomous |
| F3 | a grounding doc fails the content-quality eval | rewrite the failing doc to the judge's cited fixes — raise each flagged section to a self-explaining, product-manager-altitude statement — and re-judge until the gate passes | autonomous |
| F4 | the capability stayed directional, or a functionality lacks its entry or its doc | complete the promotion (flip the entry to detail: detailed) and emit the missing functionality entry or doc before the checkpoint | autonomous |
| F5 | an NFR need has no level/target/gate, or needs were written off the capability | re-draft each need on the capability as a concrete level + target + gate | autonomous |
| F6 | a profile dimension was lowered, or the box firmed to a state other than set | recompute the roll-up monotonic-up (max per dimension) and firm to set | autonomous |
| F7 | an out-of-box need against a set box was persisted with no approved decision | revert the box change, surface the move at the checkpoint, persist only after a human approves it | human |
| F8 | a box-move lacks its decision record, or a decision omits its dimension or from→to | write or complete the product-level decision for each move with its dimension and from→to before persisting | autonomous |
| F9 | a capability, functionality, or domain other than the target was changed (scoped-guard violation) | the guard's --restore already reverted the offending paths; re-run writing only the target capability and its functionalities, after a human confirms the restore | human |
| F10 | a slice, an epic, or a capability status flip was written | strip the prioritization over-reach — remove the slice/epic, reset the status — leaving only /understand's detailing scope |
Pause and Resume
Steps run top to bottom. On entry, resolve config, resolve the target capability 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
docs enters a dirty tree; the pre-flight clean-tree assertion (F13) is scoped to a FRESH
start — a resume continues its own in-progress delta.
Compilation Metadata
| Field | Value |
|---|
| fingerprint | sha256:f271a4ccd4a1413664ce11e204170c9a4d8bad31d2ba1b2139c4678b661bf58c (of reference/ice.md) |
| compiled_by | play-editor (#498 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 carrying box-move approvals — 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, enrich-capability-ice |
| scripts | 11 (preflight.py, lint_grounding.py, grounding_gate.py, rollup_box.py, persist_understand.py, scoped_write_guard.py, classify_change.py, gate_eval.py, distill_gate_policy.py, check_stop_condition.py, session_stamp.py) |
| step_evals | 14 (SE-1…SE-14) |
| scenario_evals | 5 (SCE-1…SCE-5) |
| recovery_entries | 13 (one per failure condition; 10 autonomous / 3 human) |
Recompiled note (#498, 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 are
removed; the enrichment skill writes per-node docs straight to the live model; the new keyed
persist_understand.py writes the shared files (spine/profile/decisions) in place; 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
(F13) and commits its own feat(model) delta after approval (C12). Order is
write-then-review (ADR 026 "Order of operations"): the full delta — LLM docs AND the keyed
persist's shared-file mutations — is written to the live model FIRST (Steps 1+4), then guarded
ONCE and classified over the full delta (Step 5), then the gate resolves over the real git diff
(Step 6), and only an approved gate COMMITS (Step 7). Nothing out-of-box is COMMITTED before
approval; cancel reverts the uncommitted writes. This ordering is why a two-dimension box-move
produces a materially different shape key from a prose edit — the shared-file delta is on disk
at classify time, not still in the manifest. 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 (#468 Stage 5, concurrent fan-out): the Step 2
content-quality judge loop was changed from a serial per-doc loop to a concurrent
read-only fan-out — one isolated judge per doc dispatched in one batch, joined before
gating (standards/rules/concurrent-fanout.md). Execution-timing change only: each judge
stays isolated and read-only, writes only its own verdict, and the gate still runs over
every verdict, so coverage (SE-4) is unchanged. No constraint, failure, scenario, or eval
touched — the ICE (reference/ice.md) and fingerprint stand. play-creator emits the same
fan-out form so a rebuild converges.
Direct-edit deviation note (#498) — 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 —
before this direct-model-write pattern is fanned out to the other 12 model-writing plays;
the fan-out must not proceed on an unverified reference. Note also: reference/ice.md C12
distinguishes the model-delta commit from the Standard Play Close (evidence + delivery report)
that /understand 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 C12 describes.