Plan the product''s vertical slices into a build sequence — order the slices /shape produced across all domains, resolve their dependencies, and estimate each one''s effort, writing only the plan onto each slice, directly in place on the live model. The planning play in the ProductOS command model, after /shape. Reads the slices to judge order and effort but writes only the plan (order, effort, dependencies). Closes the strategy pipeline through the end PR. Opens no delivery issue.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add kapilvirenahuja/garura --skill roadmap --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Roadmap?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/kapilvirenahuja-roadmap)More formats (shields.io, HTML) on the badges page.
---
name: roadmap
position: end
description: 'Plan the product''s vertical slices into a build sequence — order the slices /shape produced across all domains, resolve their dependencies, and estimate each one''s effort, writing only the plan onto each slice, directly in place on the live model. The planning play in the ProductOS command model, after /shape. Reads the slices to judge order and effort but writes only the plan (order, effort, dependencies). Closes the strategy pipeline through the end PR. Opens no delivery issue.'
user-invocable: true
---
# roadmap
Take the vertical slices /shape produced and turn the pile into a plan. /roadmap reads
every slice across every shaped domain and lays out the build sequence: the order to
build them, the dependencies between them (within and across domains), and an effort
estimate for each. It writes that plan onto each slice's **spine index entry** — `order`,
`effort`, resolved `depends_on`, and the `status` flip to `planned` — directly in place on
the live model, and touches nothing else: the slice records (composition) and every other
part of the model stay byte-identical. One run plans the whole set, because order only
means something by comparison. Convention: a lower `order` number means sooner.
**Pipeline position: end.** /roadmap CLOSES the strategy pipeline: after the ordering is persisted and verified, the D2 rule injects the close sequence `commit-change → propose-change → review-change → merge-change`, so the strategy change is committed, raised, reviewed, and merged. No `start-change` head — /vision opened the pipeline and every earlier strategy play (/understand, /shape) committed its own model delta on this branch, so /roadmap enters on an already-started branch with a clean model tree. It runs after /shape, since slices must exist before they can be planned. (#437)
**Write discipline (ADR 026, `standards/rules/direct-model-write.md`).** /roadmap writes no
per-node grounding doc at all — its LLM authoring skill (`author-roadmap`) writes NO model
file; it only drafts plan data (effort, resolved dependencies, a value preference) to STM,
which `compute_plan.py` turns into the coherent `plan.json`. Every mutation of the one
shared file /roadmap touches — the spine `_spine.yaml` slices index — is done by the
deterministic keyed persist script (`persist_roadmap.py`), in place, keyed to each planned
slice id: it writes ONLY the four plan fields (`order`/`effort`/`depends_on`/`status`) on
that slice's entry and refuses a plan that names a slice absent from the live spine (the
node-level containment the file-level scoped guard cannot see inside the shared spine).
Because the LLM never writes any model file, containment is a post-write scoped guard
(`scoped_write_guard.py`), not a draft. The model tree is asserted clean at entry (F11) and
the play commits its own `feat(model)` delta after the checkpoint (C10), before the injected
close sequence — so the working-tree diff vs HEAD is exactly this run's delta.
## Compiled From
This play was compiled from the roadmap ICE (`reference/ice.md`) by play-editor
(#466 Batch C; #467 Batch B — the checkpoint upgraded to a conditional learned gate,
see `standards/rules/gate-config.md`; #500 — migrated to direct-model-write per ADR 026
and `standards/rules/direct-model-write.md`). Intent defines constraints (C1–C10) and
failure conditions (F1–F11); the expectation defines success scenarios (S1–S6), 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
judgment — effort, resolving dependencies, and a value preference — to the
`product-os-keeper` agent via a JSON contract over files on disk, and you run every
mechanical part (reading the slices, the ordering math, the keyed in-place persist, the
post-write scoped guard, and the change-shape classifier) through bundled scripts. You
never write slice YAML by hand, you never assign the order numbers yourself, and you never
COMMIT the model delta before the single checkpoint (C8) resolves — a typed approval, a
recorded config skip, or a recorded policy auto-pass.
**Forbidden:** hand-writing slice/plan YAML; assigning order numbers in prose instead of
via `scripts/compute_plan.py`; writing the shared spine by any route other than
`scripts/persist_roadmap.py`; writing anything other than the plan fields
(order/effort/depends_on/status) on a slice's spine entry; editing a slice's composition
(its record); persisting an incoherent plan (a `compute_plan.py` non-zero exit is a hard
stop before the persist); committing the model delta before the Step 6 gate resolves;
running against a dirty product-os tree (C10/F11); touching the grounding docs, the node
tree, the profile, the lenses, decisions, or the `_deferred` bucket (C2).
**Agent boundaries:**
| Agent | Domain | Skill it invokes | Phases |
|-------|--------|------------------|--------|
| `product-os-keeper` | Read /shape's slices and judge the plan inputs — estimate effort per slice, resolve dependency_notes (+ shared functionalities + their spine depends_on) into concrete depends_on, propose a value order; draft to STM only | `author-roadmap` | Draft |
`product-os-keeper` is the single **domain agent** this play uses (1 of the ≤5 budget).
The utility work — committing, raising, reviewing, and merging the change — is the injected
end sequence (`commit-change → propose-change → review-change → merge-change`), not a domain
agent. /roadmap does **not** ground in the KB: planning is a judgment over the slices the
model already holds, not a placement question.
## Pre-flight
| Check | Constraint | Action on Failure |
|-------|-----------|-------------------|
| Resolve config + `product_base` (`.garura/core/config.yaml`) | — | Hard halt |
| **Clean model tree** — `git status --porcelain -- <product_base>product-os` is empty | C10/F11 | Hard halt (REC11) |
| The product model holds ≥1 slice to plan | C1 | Graceful exit |
Resolve config mechanically with the bundled resolver; /roadmap has no branch or issue of
its own — the branch carries /vision's strategy-pipeline issue:
```
python3 scripts/preflight.py --play roadmap --config .garura/core/config.yaml
python3 scripts/snapshot_model.py --product-base <product_base> --probe
```
`preflight.py` returns config facts (`product_base`, `stm_base`, `evidence_record`).
`snapshot_model.py --probe` reports `slice_count` **without writing anything**. If
`slice_count` is 0, exit gracefully: there is nothing to plan — run /shape first to
create slices. The probe is write-free, so re-entering pre-flight on a resume is safe.
**Clean-tree assertion (C10/F11, ADR 026).** The earlier strategy plays (/vision,
/understand, /shape) each committed their own model delta on this branch, so the product-os
tree is clean when /roadmap enters; still assert it before any write — `HEAD` is only a
correct base for the scoped guard and the change-shape if the tree is clean:
```
test -z "$(git status --porcelain -- <product_base>product-os)" || { echo "HALT: dirty product-os tree (REC11)"; exit 1; }
```
If dirty, halt and ask for a clean model tree (commit or revert the pending model edits)
before /roadmap plans.
`<working>` is the run's STM working root, `{stm_base}_shaping/roadmap/` — the plan draft,
the computed plan, the persist record (`persist-manifest.json`), the captured scoped-guard
report (`guard-report.json`), and the change-shape 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/`. The baked stop condition's relative paths resolve against
`<working>`.
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/roadmap/status/session-stamp-roadmap.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
```
**Resume check:** if `{stm_base}_shaping/roadmap/status.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 keyed persist's spine plan-field
writes — 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] Read the slices blockedBy: []
[T2] Draft plan (to STM) blockedBy: [T1]
[T3] Compute plan (coherence gate) blockedBy: [T2]
[T4] Persist (keyed, in place — spine plan fields) blockedBy: [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]
[TE1] commit-change (injected — end #1) blockedBy: [T7]
[TE2] propose-change (injected — end #2) blockedBy: [TE1]
[TE3] review-change (injected — end #3) blockedBy: [TE2]
[TE4] merge-change (injected — end #4) blockedBy: [TE3]
[T8] Scenario Validation blockedBy: [TE4]
[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: Read
**Step 1 — Read the slices** · Owner: play · Depends on: pre-flight
Read the current spine slices across every shaped domain (id, domain_ref, bundled
functionalities, dependency_notes, current plan fields) as the authoritative list of what
to plan. Under direct-model-write this is a READ of current state (HEAD is clean), not a
before/after picture — the scoped guard (Step 5), not a file census, proves
non-destructiveness:
```
python3 scripts/snapshot_model.py --product-base <product_base> --out <working>/snapshot.json
```
**SE-1 (F11/C10):** the product-os tree was clean at entry — the pre-flight assertion
(`git status --porcelain -- <product_base>product-os` empty) passed before any write, so
HEAD is a correct base for the scoped guard and the change-shape; a dirty model tree halted
(REC11). (Step 1 otherwise only reads state; the plan is exercised by Step 3 and proven by
Step 5's scoped guard.)
### Phase: Draft
**Step 2 — Draft plan (to STM)** · Owner: `product-os-keeper` · Depends on: Step 1
The agent invokes `author-roadmap` to estimate each slice's effort, resolve its
dependencies into concrete `depends_on` slice ids, and propose a value order — drafting
`plan-draft.yaml` to STM. Per ADR 026 the skill writes NO model file: `plan-draft.yaml` is
the manifest data the keyed persist (via `compute_plan.py`) later applies to the live spine:
{
"task": "for every slice in the snapshot, estimate effort, resolve depends_on (from dependency_notes + shared functionalities + their spine depends_on), and propose a value order; draft to STM only — write no model file",
"inputs": { "snapshot_path": "<working>/snapshot.json",
"product_base": "<product_base>" },
"outputs": { "plan_draft": "<working>/plan-draft.yaml" }
}
The skill reads the slices + their functionalities' grounding **read-only** and writes only
the STM draft. It assigns no order numbers and runs no topological sort — those are Step 3's
deterministic job. It writes no `_spine.yaml`, no grounding doc, no model file at all. It
returns the contract with the output path on disk — never inline content.
### Phase: Compute (coherence is a HARD gate before persist, C4)
**Step 3 — Compute plan** · Owner: play · Depends on: Step 2
Turn the draft into a coherent, dependency-correct global plan:
```
python3 scripts/compute_plan.py --snapshot <working>/snapshot.json \
--plan-draft <working>/plan-draft.yaml --out <working>/plan.json
```
`compute_plan.py` topologically orders all slices so dependencies precede dependents
(value order breaks ties), assigns the distinct integer order 1..N across every domain,
carries each slice's effort, and detects cycles. It writes the **machine coherence field**
into the plan record: `plan.json` carries `orders_coherent: true|false`.
**Coherence is a HARD gate before the persist (C4):** `compute_plan.py` MUST exit 0 (no
cycle, no missing effort, a coherent 1..N) before Step 4 runs. On a non-zero exit (a cycle
or a slice missing from the draft) apply REC4/REC5 and re-run — an incoherent plan is NEVER
persisted. This non-zero exit is also /roadmap's **blocking finding** for the Step 6 gate
(no auto-pass while it stands).
**SE-3 (F3/C3):** within the computed plan, no slice precedes one it depends_on.
**SE-4 (F4/C4):** every slice in the plan has an integer order and a non-empty effort; the
orders are distinct and form 1..N; `compute_plan.py` exited 0 (`orders_coherent: true`)
before the persist ran.
**SE-5 (F5/C5):** the plan spans every shaped domain's slices and no cycle was persisted;
on a non-zero exit (cycle or a slice missing from the draft) REC4/REC5 applied and re-ran
before Step 4.
### Phase: Persist (write the full delta first, ADR 026 write-then-review)
**Step 4 — Persist (keyed, in place — spine plan fields)** · Owner: play · Depends on: Step 3
Write-then-review (ADR 026): the FULL model delta is written to the live model BEFORE the
checkpoint, so the guard, the change-shape, and the human all see the real delta.
`persist_roadmap.py` writes the SHARED spine in place, keyed to each planned slice id: for
each slice in the coherent plan it sets ONLY the four plan fields
(`order`/`effort`/`depends_on`/`status: planned`) on that slice's spine entry, preserving
every other field and collection, and REFUSES a plan that names a slice absent from the live
spine. No draft tree, no doc copy, no before/after census. 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_roadmap.py --plan <working>/plan.json \
--product-base <product_base> --out-manifest <working>/persist-manifest.json
```
`persist_roadmap.py` writes the **machine persist record**: `persist-manifest.json` carries
`applied: true` alongside the written/changed lists — this field, not prose, is what the
close's stop-condition gate (D2) reads.
**SE-6 (F1/F2/C1/C2, F7/C7):** the persist wrote only the four plan fields on the named
spine slice entries — no non-slice entry received a plan, no slice record (composition),
grounding doc, or other collection was touched, and each written entry conforms to the spine
schema (integer order, `status: planned`, non-empty effort, resolved depends_on) — because
the keyed persist writes exactly the fields of an already-coherent plan and refuses any slice
id absent from the spine, by construction.
### 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_roadmap.py`
encoded /roadmap's write scope by construction — it only ever wrote `_spine.yaml`, and only
the slices' plan fields. Under direct-model-write that same file-level scope is the
`scoped_write_guard.py` policy: the spine is the ONLY model file /roadmap may change, so its
scope is a single `--allow`:
--allow 'product-os/_spine.yaml' # the shared spine (the keyed persist modifies its slice plan fields in place)
Every other product-model file is out of scope. Node-level containment inside the shared
spine — only the planned slices' plan fields — is kept by the keyed persist (Step 4), not
the guard.
**Guard ONCE over the full delta (C6).** After ALL writes (the keyed persist's spine
plan-field writes from Step 4), run the scoped guard a single time over the whole delta
against HEAD. Capture its report — its `ok` field is the stop condition's D3 input:
```
python3 scripts/scoped_write_guard.py --product-base <product_base> --base-ref HEAD \
--allow 'product-os/_spine.yaml' \
--out <working>/guard-report.json
```
If the guard exits non-zero (a non-spine model file changed, or a non-plan field inside the
spine), re-run with `--restore` to revert the offending paths, apply REC6, and re-persist
before the checkpoint.
**Classify the full working-tree delta (C8).** Classify the model tree's diff vs HEAD — now
the FULL delta (the spine plan-field changes), per ADR 026 write-then-review (no draft dir):
```
python3 scripts/classify_change.py --play roadmap \
--product-base <product_base> --base-ref HEAD --out <working>/shape.json
```
**SE-7 (F6/C6):** the scoped-write guard report reads `ok: true` — the model delta is
confined to the run's write scope (only `_spine.yaml` changed); no other product-model file
changed, and the run is non-destructive.
### Phase: Checkpoint (conditional gate, C8)
**Step 6 — Human review (class: standard, conditional)** · Owner: play · Depends on: Step 5
**This is the single checkpoint (C8).** It is a **conditional gate** (#467) per
`standards/rules/gate-config.md` — /roadmap is one of the eleven conditional document
plays. Resolve it first match wins: pinned (n/a here) → `gates.plays.roadmap` → 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 Steps 3 + 5 stand with no blocking finding (a `compute_plan.py` non-zero exit or a
cycle anomaly in `plan.json`, or a guard violation — /roadmap's lint-equivalent). 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 roadmap \
--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.roadmap: off` instead
records `gate skipped by config (<resolution path>)` as a Checkpoint Decisions row and
proceeds on the computed plan). When on, present the plan **inline over the real model git
diff** — the slices in order, each with its effort and its resolved dependencies, plus any
cycle anomalies for the human to break — 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 spine 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 roadmap \
--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 (F8/C8):** the model delta was written to the live spine by Step 4 but is COMMITTED
(made durable) only at Step 7 on approval — so no product-model change is COMMITTED before
this gate resolves (a typed approval, a recorded config skip, or a recorded policy
auto-pass); on cancel the whole working-tree delta is reverted to HEAD before any commit, so
nothing is left on the tree.
**SE-10 (F10):** 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 (C10, ADR 026 step 7) — a lightweight persist step that makes the writes durable
and advances HEAD, run BEFORE the injected close sequence so the subsequent `commit-change`
handles only what remains uncommitted (STM evidence, ADRs), not the model delta this play
already committed. 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): plan slices — order, effort, dependencies (#<strategy issue>)"
```
**SE-9 (F9/C9):** the close is stop-condition gated — `check_stop_condition.py` over the
baked `stop-condition.yaml` (D1 the persist record `persist-manifest.json` exists; D2 it
stamps `applied: true`; D3 the captured `guard-report.json` reads `ok: true`) must read
**held** before any COMPLETED close, and the model delta is committed (C10); a run whose
keyed persist or scoped guard did not land closes HALTED, never COMPLETED (REC9).
### Phase: End sequence (injected — D2 position: end)
The persisted ordering is a durable model change; the standard end sequence closes the
strategy pipeline (#437). Each member runs as a sub-play dispatched with `parent_run_id`
(emits only its own C1 evidence; this play's close absorbs it). Each member resolves its
own context from the branch and config; this play passes no hand-rolled git/PR/merge
logic. The `feat(model)` model delta was already committed at Step 7, so `commit-change`
handles only what remains uncommitted (STM evidence, ADRs).
**Step E1 — commit-change** · Owner: `commit-change` (sub-play) · Depends on: Step 7 —
commit the remaining strategy-pipeline changes grouped by concern; no push.
**Step E2 — propose-change** · Owner: `propose-change` (sub-play) · Depends on: Step E1 —
self-review, push the branch, open the PR.
**Step E3 — review-change** · Owner: `review-change` (sub-play) · Depends on: Step E2 —
diff-scoped review, approve/reject verdict. A reject stops the sequence before merge.
**Step E4 — merge-change** · Owner: `merge-change` (sub-play) · Depends on: Step E3
(approve verdict) — merge the PR, switch to main and pull, delete the feature branch.
{
"play": "<commit-change | propose-change | review-change | merge-change>",
"parent_run_id": "<this run id>",
"inputs": {},
"outputs": { "result": "{stm_base}_roadmap/end/<member>.json" }
}
### Phase: Scenario Validation
**Step 8 — Scenario evals** · Owner: play · Depends on: the end sequence
- **SCE-1 (S1 — planner, first plan):** every non-deferred slice's spine entry has an
integer order and a non-empty effort; orders are distinct 1..N; the scoped-guard report
reads `ok: true` (only `_spine.yaml` changed, only the plan fields); the stop-condition
verdict reads held.
- **SCE-2 (S2 — architect, dependencies):** for every resolved `A depends_on B`,
order(B) < order(A); any cycle is in the checkpoint anomaly list and no order persisted
for it (`plan.json` anomalies + the persisted spine).
- **SCE-3 (S3 — delivery lead, effort):** every planned slice carries an effort estimate on
its persisted spine entry.
- **SCE-4 (S4 — product manager, cross-domain):** the orders form a single 1..N spanning
slices from every shaped domain, not a per-domain sequence.
- **SCE-5 (S5 — product owner, re-run non-destructive):** on an unchanged model the persist
manifest's `written` list is empty and the guard report reads `ok`; on a changed model only
the plan fields differ on any slice and the guard confirms no other model file changed.
- **SCE-6 (S6 — reviewer, the checkpoint):** the Step 6 checkpoint showed the ordered slices
with effort and dependencies and any anomalies, over the real model git diff, and no
product-model change was COMMITTED before that gate resolved — on cancel the working tree
returns byte-clean to HEAD — or, on the auto-pass path (a policy-listed shape), the gate
resolved with no wait and the recorded auto-pass, the appended ledger line, and the diff
summary stand in the approval's place.
### Phase: Evidence & Close
**Step 9 — Close** · Owner: play · Depends on: Step 8
Run the Standard Play Close. /roadmap is a **product-scoped** play (no issue of its own) —
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`).
**SE-9 (F9/C9):** the close is stop-condition gated — `check_stop_condition.py` over the
baked `stop-condition.yaml` (D1 the persist record exists; D2 it stamps `applied: true`;
D3 the captured guard report reads `ok: true`) reads **held** before the run closes
COMPLETED, and the model delta is committed (C10); anything else closes HALTED with the
unmet clauses named.
```bash
# --- Standard Play Close (canonical; see standards/rules/play-close.md) ---
# Path tokens resolved at pre-flight (resolve here if not already):
# ltm_project_target = yq '.ltm.project-target' .garura/core/config.yaml
# evidence_base, slug:
# project-scoped play : evidence_base="${stm_base}${issue}/evidence/roadmap/" ; slug="#${issue}"
# product-scoped play : evidence_base="${product_base}_evidence/roadmap/" ; 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/roadmap/status/session-stamp-roadmap.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Evaluate the Done means against the run's STM working root as the
# close's authoritative input.
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}_shaping/roadmap/" \
--out "${stm_base}_shaping/roadmap/status/stop-condition-roadmap.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
```
`/roadmap` is product-scoped: `evidence_base="${product_base}_evidence/roadmap/"` and
`slug="${product_slug}"` (the product, since one run plans the whole set).
**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 REC9 (re-run
`persist_roadmap.py` over the coherent plan, re-capture the scoped-guard report) 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.roadmap`; 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 `roadmap`, run_id `roadmap-${ts}`,
product_slug, started_at/completed_at, status per C0, exit_reason; artifacts produced: the
plan draft, the computed plan, the persist manifest (`persist-manifest.json`), the captured
`guard-report.json`, the model-delta commit sha, the slices planned, the stop-condition
verdict; step and scenario eval results SE-3…SE-10 / SCE-1…SCE-6; checkpoint decision from
Step 6 including the order and any anomalies (incl. any `gate skipped by config` /
`gate auto-passed by learned policy` row) plus the gate ledger line(s) appended this run; the
session identity stamp fields from $session_stamp (#463): session_id, ledger_file,
ledger_start_offset, ledger_end_offset (null when unresolved — never blocks the close);
and stop_condition per C0 with the Stop Condition section filled) and write to
`$evidence_dest`. Do NOT hand-author the body.
**Step C2 — Render delivery report.** Also render the **Next** line: resolve this play in `standards/rules/pipeline-next.md` and emit `**Next:** /<command> — <why>. Or run /next to see all recommended actions.` (only /next pointer, or omit, when the mapped command is null), per `play-close.md`. Fill the `delivery-report.md` slots and output the
report: `## roadmap Delivered — ${product_slug}`, the Run Summary table, the Pipeline
Steps table from the task DAG, the Artifacts Produced table (the ordered slices with
effort + dependencies, any anomalies, the model-delta commit), Next Steps (run /grill on the
top-ordered slice's functionalities to cut epics), and a pointer to `$evidence_dest`. Always
emitted; never gated.
```bash
# --- end Standard Play Close ---
```
## Scenario Validation
| Scenario | Persona | Eval |
|----------|---------|------|
| S1 — first plan | planner | SCE-1 |
| S2 — dependencies | architect | SCE-2 |
| S3 — effort | delivery lead | SCE-3 |
| S4 — cross-domain | product manager | SCE-4 |
| S5 — re-run non-destructive | product owner | SCE-5 |
| S6 — the checkpoint | reviewer | SCE-6 |
## Recovery
| For | Trigger | Direction | Handoff |
|-----|---------|-----------|---------|
| F1 | a plan was written onto a non-slice artifact | the keyed persist refuses a non-slice by construction; revert and re-run the persist over the coherent plan only | autonomous |
| F2 | a write touched a slice's composition (record) or another part of the model | the guard's `--restore` reverted the out-of-scope write; re-run the keyed persist writing only order/effort/depends_on/status on the spine slices, after a human confirms the restore | human |
| F3 | a slice is ordered ahead of one it depends_on | re-run `compute_plan.py` so dependencies precede dependents before persisting | autonomous |
| F4 | an incoherent plan (a planned slice lacks order or effort, or the orders aren't a coherent 1..N) | recompute a complete, coherent plan before the keyed persist; the persist is gated on `compute_plan.py` exit 0 | autonomous |
| F5 | a dependency cycle, or a domain's slices left unplanned | surface the cycle at the checkpoint for a human to break, and include every shaped domain's slices in the single plan | human |
| F6 | the scoped-write guard reports an out-of-scope path (a non-spine model file, or a non-plan field inside the spine) | the guard's `--restore` already reverted the offending paths; re-run the keyed persist writing only the plan fields on the spine slices, after a human confirms the restore | human |
| F7 | a written slice spine entry fails the spine schema | re-derive a coherent plan and re-run the keyed persist so the entry conforms before the play completes | autonomous |
| F8 | the model delta was committed before the checkpoint gate resolved, or a cancelled checkpoint left writes on the working tree | revert the premature commit and the working-tree writes (`scoped_write_guard.py --restore`, empty allow set) and re-present the checkpoint; commit only after the gate resolves | human |
| F9 | the run is about to close COMPLETED with the Done means unmet (a missing persist record, `applied` not true, or the guard report absent or not ok) | produce the missing artifact — re-run `persist_roadmap.py` over the coherent plan so the persist record carries the machine `applied` field, re-capture the guard report — then re-evaluate the stop condition; the close stays HALTED until the verdict reads held | autonomous |
| F10 | a conditional-gate crossing left no live-eval ledger line, or an auto-pass fired for a shape the policy does not list as auto (or that carried a blocking finding) | re-append the missing ledger line for the recorded crossing; when the auto-pass was unearned, re-run the gate as a live wait — render the approval prompt and wait for the typed response — before proceeding | autonomous |
| F11 | the product-os tree is dirty at entry (uncommitted model edits present) | halt at pre-flight and ask for a clean model tree — commit or revert the pending model edits — before /roadmap plans | human |
## Pause and Resume
Steps run top to bottom. On entry, resolve config, run the write-free pre-flight probe,
check the status marker (`{stm_base}_shaping/roadmap/status.json`), skip completed steps,
reset any in-progress step to pending, and continue. The pre-flight clean-tree assertion
(F11) is scoped to a FRESH start — a resume that already wrote the spine plan fields
continues its own in-progress delta. A fresh start with no marker runs everything.
## Compilation Metadata
| Field | Value |
|-------|-------|
| fingerprint | sha256:11d120b0c33fd8faaf290bcfe75bbf6cbce0d6b46e45ca380a05d00c5e5a2132 (of `reference/ice.md`) |
| compiled_by | play-editor (#500 direct-model-write, ADR 026); prior: play-editor (#467 Batch B); play-editor (#466 Batch C) |
| pipeline_position | end (commit-change → propose-change → review-change → merge-change tail; opened by /vision) |
| position_exception | model-writing end play — writes the model in place on the branch and commits its own `feat(model)` delta (C10) BEFORE the injected close sequence; commit-change then handles only the STM remainder |
| workflow_structure | A (single checkpoint, class standard, conditional gate per gate-config.md #467; direct-model-write WRITE-THEN-REVIEW per ADR 026 — persist + guard + classify before the gate, commit after; stop-condition gated close) |
| stop_condition | stop-condition.yaml (D1–D3), gate live at Step C0 |
| domain_agents | 1 (product-os-keeper) |
| utility_agents | 0 (the end sequence is injected sub-plays, not domain agents) |
| skills_used | author-roadmap |
| scripts | 9 (preflight.py, snapshot_model.py — reads current slices, compute_plan.py — writes the machine `orders_coherent` field and hard-gates the persist, persist_roadmap.py — keyed in-place persist writing the machine `applied` field, scoped_write_guard.py — post-write containment, classify_change.py — #467 shape classifier (git mode), gate_eval.py — #467 live-eval ledger, distill_gate_policy.py — #467 policy learner, check_stop_condition.py — Done-means gate, session_stamp.py — #463 identity stamp) |
| step_evals | 10 (SE-1, SE-3…SE-10; SE-2 folded into SE-6) |
| scenario_evals | 6 (SCE-1…SCE-6) |
| recovery_entries | 11 (one per failure condition; 6 autonomous / 5 human) |
**Recompiled note (#500, direct-model-write / ADR 026):** migrated from draft-then-apply to
direct-model-write. The old `apply_roadmap.py` promotion step and the `validate_roadmap.py`
before/after verify are removed; `snapshot_model.py` was slimmed to a current-slices read
(its file census / `spine_before` before-picture are gone — the scoped guard now proves
non-destructiveness). The authoring skill (`author-roadmap`) writes NO model file — it drafts
plan data to STM, which `compute_plan.py` turns into the coherent `plan.json`; the new keyed
`persist_roadmap.py` writes the spine slices' plan fields in place, keyed by slice id, and
refuses a plan naming a slice absent from the live spine. Containment is the post-write
`scoped_write_guard.py` (`--allow 'product-os/_spine.yaml'` — the single file /roadmap
touches; 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`. Order is **write-then-review** (ADR 026 "Order of operations"): the full delta is
written to the live spine FIRST (Step 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 (or
auto-passed / config-skipped) gate COMMITS (Step 7). Nothing is COMMITTED before the gate;
cancel reverts the uncommitted writes. The play asserts a clean product-os tree at entry (F11)
and commits its own `feat(model)` delta after the checkpoint (C10), BEFORE the injected close
sequence — which then handles only what remains uncommitted (STM evidence, ADRs). The Done
means moved from the plan-draft/plan/orders_coherent triple to the canonical persist + guard
triple (D1 persist record exists; D2 `applied: true`; D3 guard `ok: true`); plan coherence is
now a HARD pre-persist gate (`compute_plan.py` exit 0) and the Step 6 gate's blocking finding,
not a Done clause. The strategy-chain close (the injected `commit-change → propose-change →
review-change → merge-change` end sequence, opened by /vision) is preserved unchanged. See
`standards/rules/direct-model-write.md`.
**Recompiled note (#467 Batch B):** checkpoint upgraded to a conditional learned gate;
see `gate-config.md`.
**Direct-edit deviation note (#500) — INTENT CHANGE, HAND-COMPILED, CONVERGENCE UNVERIFIED:**
This SKILL was updated to the direct-model-write write-then-review shape (ADR 026) by a
**hand-compile from `reference/ice.md`**, NOT by a `/play-editor` run. This is an intent
change (it alters the write path, the containment guarantee, the checkpoint cancel semantics,
the Done means, and the step order), so the sanctioned path is recompile-via-`/play-editor`;
play-editor is interactive-only (fully gated, human-checkpoint) and cannot run headless in this
environment, so the compiled output was produced by hand to match what play-editor would emit
from the current `reference/ice.md` (fingerprint above). **The `compiled_by` line names
play-editor for provenance intent, but no play-editor run actually occurred and convergence is
UNVERIFIED.** An interactive `/play-editor` convergence run against `reference/ice.md` is
**REQUIRED** — confirming the emitted SKILL matches this hand-compiled body and refreshing the
fingerprint. This mirrors the same caveat on /understand's #498 migration (the ratified
reference implementation) and its /vision, /grill, /learn fan-out: roadmap is the last strategy
play in that fan-out and remains convergence-unverified until an interactive play-editor run
confirms it.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!