Skip to content
Back to skills

Start Change

ASecurity

Open a unit of work cleanly — the change the person names (an issue number, a short description, or plan <id>; nothing typed starts nothing) — resolve or create the tracked issue, cut its branch (under its plan''s branch for an issue in a plan, read from main and the plan''s branch; else a feature branch off up-to-date main), set up a git worktree when config calls for it, and initialize the STM workspace. The start-of-pipeline play in the ProductOS command model. Also starts a plan''s work o...

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
toolspythongobashgit

Works with

  • cli

Security analysis

A100/100

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

Scanned November 7, 2026

npx -y skills add intent-driven-ai/garura --skill start-change --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Start Change?

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

Security grade badge for Start Change
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/intent-driven-ai-start-change/badge)](https://www.skillsdirectory.com/skills/intent-driven-ai-start-change)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: start-change
position: start
description: 'Open a unit of work cleanly — the change the person names (an issue number, a short description, or plan <id>; nothing typed starts nothing) — resolve or create the tracked issue, cut its branch (under its plan''s branch for an issue in a plan, read from main and the plan''s branch; else a feature branch off up-to-date main), set up a git worktree when config calls for it, and initialize the STM workspace. The start-of-pipeline play in the ProductOS command model. Also starts a plan''s work on its own (plan <id>): main''s plan file first, then the plan''s branch, and no story branch. Use when beginning work on a change, starting an issue, kicking off a feature, or starting a plan.'
user-invocable: true
---

# start-change

Open a unit of work cleanly — the one the person names, never the branch they are on: ensure
a tracked issue, a branch for it — a sub-branch of its
plan's branch (`plan/<plan-id>/<issue>-<slug>` off `plan/<plan-id>/base`) when the issue
sits in a plan, else `feature/<issue>-<slug>` cut from an up-to-date main — a git worktree
when config calls for it, and an initialized STM workspace — so every play that runs after
start-change begins from a known, identified context. It is never locked to a plan: any issue
(a bug, a chore, a story, a feature), by its number or a short description, starts the same way.
On top of that, `plan <id>` starts a plan's work on its own — main's plan file first, then the
plan's branch — and stops, with no story branch (#989).

**Pipeline position: start.** start-change is the start-of-pipeline primitive. The D2
pipeline-position rule injects it into any other play declared `position: start`. As the
primitive itself, start-change does not get a start composed into it.

## Compiled From

This play was compiled from the start-change ICE (`reference/ice.md`) by edit-play
(#466 Batch A, Level 3 rollout per ADR 025; #467 Batch C gate-off recompile; #484 scripted
chain; #731 plan branches; #779 plan read from git; #953 safe stale-branch delete; #989 the
change is what is typed, and a plan starts on its own). Intent defines constraints (C1–C11) and
failure conditions (F1–F11); the expectation defines success scenarios (S1–S13), a Done means
(D1–D4 for a change, baked to `stop-condition.yaml`; D5–D8 for a plan's start, baked to
`stop-condition-plan.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 workflow and the step order. **Issue** work goes to a
utility agent (the fresh-issue match from a description is genuine judgment); the mechanical
**branch** work runs in a bundled script calling git directly, not an agent (C8, #484). You
never reason out git/gh inline in play prose.

**Forbidden:** running the mechanical branch work (cut/worktree/push) through an agent
dispatch (the #484 bug); ad-hoc `gh`/`git` in play prose; reimplementing issue logic inline;
taking the change from the branch name, or going on when nothing was typed (C10); cutting a
plan's branch any way but the plan tool's `plan_branch.py`, main first (C9/C11).

**Agent boundaries:**

| Agent | Domain | Skill it invokes | Phases |
|-------|--------|------------------|--------|
| `project-orchestrator` | Issues: resolve an existing issue, create a new one (fresh-match judgment) | `track-issue` | Execution |

`project-orchestrator` is a **utility agent** (issue plumbing + the fresh-issue match),
exempt from the domain-agent budget. The branch work is the bundled `setup_branch.py`, and
every cut of a plan's branch is the plan tool's `plan_branch.py` — no agent (C8). A plan's start
(`plan <id>`) dispatches no agent at all. This play uses **zero domain agents**.

## Pre-flight

| Check | Constraint | Action on Failure |
|-------|-----------|-------------------|
| Resolve working base + config (`.garura/core/config.yaml`) | — | Hard halt |
| Git repository present | C2 | Hard halt |
| Platform CLI available (`gh`) | C1 | Hard halt |
| What the person typed: an issue number, a description, or `plan <id>` — via `scripts/resolve_args.py` | C10 / C11 | **Hard halt** on nothing typed, or a plan that cannot start — print its `error` line; nothing is started |
| The plan tool is installed (`.garura/core/tools/plan/plan_branch.py`) | C9 / C11 | Hard halt |
| Read `start-change.worktree` flag (default `false`) | C3 | — |
| The plan files' folder is the resolver's `plans_base` (`plans.base-path`, default `.garura/project/plans/`); a plan is read from git at the latest main and the plan's own branch, never the checkout | C9 | — |
| Resume marker check for this issue's start-change | C6 | Resume |

Resolve the deterministic pre-flight facts with the bundled resolver — not by inference. The
orchestrator captures the current branch and passes it in:

```
python3 scripts/preflight.py --play start-change \
    --config .garura/core/config.yaml --branch "$(git branch --show-current)"
```

It returns the facts as JSON — `stm_base`, `worktree` (the `start-change.worktree` flag,
absent ⇒ false), `plans_base` (the plan files' folder), `branch`, `issue`, `on_default_branch`,
`evidence_record`, `ltm_project_target`. The orchestrator holds these for every later path;
the table keeps the policy. The live checks (git repo present, `gh` available) stay as the
orchestrator's environment reads, not the resolver. **The resolver's `issue` (read from the
branch name) never names the change (C10):** the change is what the person typed.

Then read what the person typed — the play's arguments, as they are:

```
python3 scripts/resolve_args.py --args="<the play's arguments>" --plans-dir "<plans_base>" \
    --description-out "<stm_base>_start/work-description.txt" \
    --out "<stm_base>_start/args.json"
test -f .garura/core/tools/plan/plan_branch.py || { echo "the plan tool is not installed — halt"; exit 1; }
```

`args.json` reads `mode`: `issue` (a number; words after it are a `note`), `description`
(words; written to `work-description.txt`), `plan` (`plan <id>`, read from main in git), or
`none`. Exit 1 — nothing typed, or a plan no file on main names, or main marks Done or Dropped —
is a **hard halt**: print its `error` line to the person and start nothing (REC10; S10, S11).
Exit 2 is a hard halt with the error named. `mode: plan` takes the **Plan path** below; every
other mode takes the **Change path** (Steps 1–5). `<stm_base>_start/` is a scratch folder: its
files move into the issue's STM at Step 5, or into the plan's working folder on the Plan path.

**Resume check (C6, F5):** when the typed issue already has a status marker or an existing
`feature/<issue>-*` or `plan/<plan-id>/<issue>-*` branch, resume — switch to the existing
branch, reuse the existing STM, and do NOT create a second issue or branch. Skip completed
steps, reset any in-progress step to pending, continue from the first incomplete one. The
branch the session is on never makes a resume (C10).

## Task DAG

Create ALL tasks immediately after resolving the working base — before any domain work.
The play owns this DAG; agents must not edit its top-level tasks.

```
Change path (mode issue | description):
[T1] Resolve Issue              blockedBy: []
[T2] Confirm New Issue          blockedBy: [T1]
[T3] Create / Finalize Issue    blockedBy: [T2]
[T4] Set Up Branch + Worktree   blockedBy: [T3]
[T5] Initialize STM Workspace   blockedBy: [T4]
[T6] Scenario Validation        blockedBy: [T5]
[T7] Close                      blockedBy: [T6]

Plan path (mode plan):
[T1] Start the Plan's Branch    blockedBy: []
[T2] Check the Plan's Start     blockedBy: [T1]
[T3] Scenario Validation        blockedBy: [T2]
[T4] Close                      blockedBy: [T3]
```

Create only the tasks of the path `args.json` names.

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: Execution

**Step 1 — Resolve Issue** · Owner: `project-orchestrator` · Depends on: pre-flight
Dispatch a JSON contract; the agent invokes `track-issue` (`action: resolve`) to find
an existing issue matching the work (or the issue number passed in), and writes the
result to disk. The contract moves only paths, never data:

    {
      "task":    "resolve an existing tracked issue for this change (do not create)",
      "inputs":  { "description": "<working>/work-description.txt",
                   "issue_number": "<args.json issue, when mode is issue>" },
      "outputs": { "issue": "<working>/issue.json" }
    }

The issue comes only from `args.json` (C10): its `issue` for `mode: issue`, else the match the
agent finds for the typed description (it confirms the issue — a bug, a feature, a chore, a
story — or reports none). For `mode: issue`, `<working>/work-description.txt` holds the
issue's title and the typed `note`, when there is one.

`issue.json` records `{ resolved: true|false, issue_number, title, created: false }`.

**Step 2 — Confirm New Issue** · Owner: play · Depends on: Step 1
*Skip when `issue.json.resolved == true`.* When no issue was resolved, creating one is an
outward-facing action (class: standard). Resolve the gate switch per
`standards/rules/gate-config.md` FIRST:

- **off** (this project: `gates.plays.start-change: off`) → do not wait; record
  `gate skipped by config (gates.plays.start-change)` as a Checkpoint Decisions row in
  evidence and proceed. A skip is always recorded, never silent.
- **on** → present the proposed new issue (title + summary) and wait for a typed
  approval. Approve → continue to Step 3; cancel → halt.

**Either way — the machine wall (C1/F7), never gated by the switch:** before Step 3
creates anything, run the bundled validator on the proposal record:

```
python3 scripts/validate_issue.py \
    --issue-json <working>/issue.json \
    --description <working>/work-description.txt
```

It checks the title is present, ≥15 chars, and tagged (`[ENH]/[BUG]/[FEAT]/[DOCS]/[TASK]`
or a conventional prefix), and that the work description is complete. A non-zero exit is
a **hard halt** regardless of the gate state — REC7 hands to a human (fix the
title/description, re-run). This is the deterministic check that replaced the human eye
when the gate went off (#467).

**Step 3 — Create / Finalize Issue** · Owner: `project-orchestrator` · Depends on: Step 2
When Step 1 resolved an issue, carry it forward unchanged. When Step 2 approved a new
issue, the agent invokes `track-issue` (`action: create`) and writes the finalized
`issue.json` (`created: true`, `issue_number` set).
**SE-1 (F1/C1):** `issue.json` has a non-empty `issue_number` (an issue is anchored
before any branch work); on resume, `created == false` and no new issue exists (F5).
**SE-8 (F7/C1):** `validate_issue.py` ran BEFORE the create (its verdict predates
`created: true`), and it exits 0 when re-run on the created issue's record — title
present/tagged, description complete. A non-zero exit at either point halted the run
to a human (REC7); no issue exists over a failed validation.

**Step 4 — Set Up Branch + Worktree** · Owner: play (script) · Depends on: Step 3
First read which plan the issue sits in, from the plan files — never guess it (C9):

```
python3 scripts/resolve_plan.py --plans-dir "<pre-flight plans_base>" \
    --issue <issue_number> --slug <slug> --out "<working>/plan.json"
```

It fetches origin and reads the plan files from git, never from the checked-out branch: at the
latest `origin/main`, and, for a plan whose own branch `plan/<plan-id>/base` is on origin, on
that branch too (a plan session writes there first); an issue in either copy belongs to the plan.
It returns `{ plan_id, plan_branch, issue_branch, source }` — `plan/<plan-id>/base` and
`plan/<plan-id>/<issue>-<slug>` for an issue in a plan, or `plan_branch: null` and
`feature/<issue>-<slug>` for an issue in no plan; `source` names the ref read, whether the fetch
worked (`fetched: false` means the last fetched copies were read — say so in the report) and each
plan branch read. Exit 2 (the issue sits in two plans, a plan file cannot be read, or there is
no `origin/main`) is a hard halt — REC9.

Then run the bundled branch script with those names. It pulls `main` to its latest origin
tip. For an issue in a plan whose branch is not on origin yet, it has the plan tool cut
`plan/<plan-id>/base` main first (C9, ADR 061): main's plan file is set to In Progress with the
branch named, committed and pushed on main, and the branch is cut from that commit. When the
plan's branch exists, it merges the latest main into it. It then cuts the issue's branch from
it; a merge conflict exits 1 and halts for a person. A plan branch gone on origin but
still here is deleted and cut fresh only when every commit on it is on main — the plan tool's rule — and branch.json
records it (`stale_plan_branch: {deleted, tip, on_main}`); otherwise, or when origin cannot be
reached, it exits 1, keeps the copy and names why (the commits that are not on main). A push or
a delete that fails exits 1; a call to origin gives up after a time limit. For an issue in no plan it cuts
`feature/<issue>-<slug>` from main. It switches to the branch if it already exists, sets up
a git worktree **iff** `start-change.worktree` is true, pushes, and writes `branch.json`.
No agent (C8):

```
python3 scripts/setup_branch.py --issue <issue_number> \
    --branch-name "<plan.json issue_branch>" --base main \
    $( [ "<plan.json plan_branch>" != "null" ] && echo --plan-branch "<plan.json plan_branch>" \
         --plan-tool .garura/core/tools/plan/plan_branch.py --plans-dir "<plans_base>" \
         --ignore "<stm_base>" ) \
    $( [ "<start-change.worktree>" = "true" ] && echo --worktree ) \
    --out "<working>/branch.json"
```

`branch.json` records `{ branch_name, base_sha, on_default_branch: false, worktree_path|null,
plan_branch|null, plan_branch_created }`.
**SE-2 (F2/C2):** the current branch is not `main` and equals `plan.json.issue_branch`. For an
issue in no plan, `branch.json.base_sha` equals the current `origin/main` tip. For an issue
in a plan, origin/main is an ancestor of `plan_branch`, and `base_sha` equals its tip.
**SE-10 (F9/C9):** when `plan.json.closed_plan` is set (the issue sits in a plan main marks Done or
Dropped), `plan_id` is null, `branch.json.branch_name` starts with `feature/`, and no
`plan/<closed_plan>/base` was made; when `branch.json.plan_branch_created` is true, `plan-branch.json` (the plan
tool's record, beside `branch.json`) reads `action: cut` with a `main_commit`, and main's plan
file at that commit reads `status: In Progress` and `branch: plan/<plan_id>/base` — the plan's
branch was cut main first, through the plan tool. `plan.json` exists and was written by `resolve_plan.py` before the branch
was cut, and its `source.ref` is `origin/main` (read from git, not the checkout); when
`source.fetched` is false, the delivery report says the plan was read from the last fetched
copies; when `branch.json.stale_plan_branch` is set, its `tip` is an ancestor of `origin/main`
(no commit was lost); when `plan.json.plan_id` is set, `branch.json.branch_name` starts with
`plan/<plan_id>/` and `branch.json.plan_branch` is `plan/<plan_id>/base`.
**SE-3 (F3/C3):** `git worktree list` shows a worktree for the branch **iff**
`start-change.worktree` is true (present when on, absent when off).
**SE-6 (C5):** issue work went through the `track-issue` skill (`issue.json` exists) and
the branch work through the bundled `setup_branch.py` (`branch.json` exists) — no inline
`gh`/`git` in play prose.
**SE-9 (F8/C8):** the branch cut/worktree/push ran as `setup_branch.py`, not an agent
dispatch — no `repo-orchestrator`/`cut-branch` was invoked for the branch work.

**Step 5 — Initialize STM Workspace** · Owner: play · Depends on: Step 4
Record the session identity stamp's start marker first (#463 — soft-fail, never a halt); the stamp start runs here rather than at pre-flight because pre-flight has no issue yet:

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

Run the mechanical script (it creates the five permitted structure keys idempotently):

```
python3 scripts/init_stm.py --stm-base <stm.base-path> --issue <issue_number>
```

Move the scratch files of `<stm_base>_start/` (`args.json`, and `work-description.txt` when a
description was typed) into `{stm_base}{issue}/context/`. Then commit the run artifacts on the fresh feature branch (C7) — play-owned mechanical
work, run as a command: the workspace opens with a clean tree and its own record
(issue.json, branch.json, work-description) aboard:

```
git add "{stm_base}{issue}/context/"
git commit -m "chore(stm): record start-change workspace context (#<issue>)"
```

**SE-4 (F4/C4):** `<stm.base-path>/<issue_number>/` exists and contains the five
structure dirs (`specs`, `evidence`, `checkpoint`, `context`, `review`) before close.

**SE-11 (F10/C10):** `args.json` exists and was written by `resolve_args.py` before Step 1;
its `mode` is `issue` or `description`, and `issue.json`'s `issue_number` is `args.json`'s
`issue` (mode issue) or the agent's match for the typed description — never the number in the
branch name. For `mode: none` the run halted at pre-flight with no issue, branch or STM made.

### Plan path — `plan <id>` (C11)

The working folder is `<working> = ${stm_base}_plans/start-change/<id>/`, made fresh; it is never
committed. Move `<stm_base>_start/args.json` into it, and record the session identity stamp's
start marker there (#463 — soft-fail, never a halt):

```
python3 scripts/session_stamp.py --phase start \
    --marker "<working>/session-stamp-start-change.json" \
    --cwd "$(pwd)" --branch "$(git branch --show-current)"
```

**Step P1 — Start the Plan's Branch** · Owner: play (script) · Depends on: pre-flight
Record the branches, then get onto the plan's branch with the plan tool — the one way to cut
it, main first (ADR 061):

```
python3 scripts/check_plan_start.py --save-branches <working>/branches-before.txt
python3 .garura/core/tools/plan/plan_branch.py go --plan <id> --plans-dir "<plans_base>" \
    --ignore "<stm_base>" --out <working>/plan-branch.json
```

Exit 0 reads `action: cut` (main's plan file set to In Progress with `branch: plan/<id>/base`,
committed and pushed on main; the branch cut from that commit and pushed), `switch` or `stay`
(the branch exists; nothing changes). Exit 3 (uncommitted work, the files named) or 2 (a Done or
Dropped plan, a stale copy with commits not on main, a git step that failed) is a **hard halt**:
print the record's `error` and `files`, and start nothing. No issue is looked up, no story
branch is cut, no STM workspace is made, and nothing is committed on the plan's branch.

**Step P2 — Check the Plan's Start** · Owner: play (script) · Depends on: Step P1

```
python3 scripts/check_plan_start.py --record <working>/plan-branch.json --plans-dir "<plans_base>" \
    --branches-before <working>/branches-before.txt --out <working>/plan-start-check.json
```

**SE-12 (F11/C11):** `plan-start-check.json` reads `ok: true`: `on_plan_branch` (the session is
on `plan/<id>/base`, and it is on origin), `main_agrees` (for a cut: the branch equals the main
commit it was cut from, which is on origin/main, and main's plan file there reads In Progress
on the branch; for a switch or stay: true) and `no_story_branch` (no `plan/<id>/…` branch but
the base was made). A problem applies REC11.

The Plan path then runs **SCE-8** and **SCE-9** (Step 6) and the Close (Step 7) with
`stop-condition-plan.yaml` over `<working>`.

### Phase: Scenario Validation

**Step 6 — Scenario Evals** · Owner: play · Depends on: Step 5
- **SCE-1 (S1 — developer, fresh start):** a new issue, a `feature/<issue>-*` branch off
  the current `origin/main` tip, and the STM workspace all exist; the run artifacts are
  committed on the branch; the stop-condition verdict reads held.
- **SCE-2 (S2 — developer, existing issue):** the provided issue was resolved (no new
  issue created), its branch exists, and STM exists; the run artifacts are committed on
  the branch; the stop-condition verdict reads held.
- **SCE-3 (S3 — developer, resume):** on a re-run, issue count and branch count are
  unchanged and the session ends on the existing branch.
- **SCE-4 (S4 — developer, worktree by config):** `git worktree list` shows the branch's
  worktree when `start-change.worktree` is true, and shows none when it is false.
- **SCE-5 (S5 — developer, first issue of a plan):** `plan/<P>/base` and
  `plan/<P>/<issue>-<slug>` both exist on origin; `plan/<P>/base` equals the origin/main tip;
  `branch.json` reads `plan_branch: plan/<P>/base`, `plan_branch_created: true`.
- **SCE-6 (S6 — developer, plan already started):** origin/main is an ancestor of
  `plan/<P>/base`; `branch.json.base_sha` equals the `plan/<P>/base` tip;
  `plan_branch_created: false`.
- **SCE-7 (S7 — developer, an issue added on main while the checkout is older):** `plan.json`
  reads `plan_id: <P>` with `source.ref: origin/main`, and `branch.json.branch_name` starts
  with `plan/<P>/`.
- **SCE-8 (S8 — product owner, start a plan on its own):** `plan-start-check.json` reads
  `ok: true`, `action: cut`, `main_agrees: true` and `no_story_branch: true`; the
  stop-condition verdict over `stop-condition-plan.yaml` reads held.
- **SCE-9 (S9 — product owner, start a plan again):** `plan-start-check.json` reads `ok: true`
  and `action: switch` or `stay`; `plan-branch.json` has no `main_commit`.
- **SCE-10 (S10 — anyone, a wrong plan number):** `args.json` reads `mode: plan` and a
  non-empty `error`, and the run halted at pre-flight: no `plan-branch.json` exists.
- **SCE-11 (S11 — developer, nothing typed):** `args.json` reads `mode: none` and the run
  halted at pre-flight: no `issue.json`, no `branch.json` and no STM workspace exist.
- **SCE-12 (S12 — developer, any change on a plan's branch):** on `plan/<P>/base` with a
  description typed, `args.json` reads `mode: description` and `issue.json`'s
  `issue_number` is not `<P>`.
- **SCE-13 (S13 — developer, a bug in a finished plan):** `plan.json` reads `plan_id: null` and
  `closed_plan: <P>`; `branch.json.branch_name` starts with `feature/`; no `plan/<P>/base` was made.
- **SE-5 (F5/C6):** a re-run for the same issue created neither a duplicate issue nor a
  duplicate branch (counts unchanged vs. before the run).

### Phase: Evidence & Close

**Step 7 — Close** · Owner: play · Depends on: Step 6
Run the Standard Play Close. It records evidence (when recording is on) and renders the
user-facing delivery report. Evidence recording is play-only and config-gated per the D1
evidence rule (`standards/rules/evidence-recording.md`).
**SE-7 (F6/C7):** the stop-condition verdict is held before the run closes COMPLETED;
the run artifacts are committed on the feature branch.

```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/start-change/"   ; slug="#${issue}"
#   Plan path — every ${stm_base}${issue}/ path below reads <working>/ instead:
#     evidence_base="${stm_base}_plans/evidence/start-change/" ; slug="plan ${id}";
#     the stamp marker is <working>/session-stamp-start-change.json, and the stop condition runs
#     with --manifest "<play-dir>/stop-condition-plan.yaml" --base "<working>/" (D5–D8)

# Session identity stamp (#463) — close phase; start phase ran at Step 5
session_stamp=$(python3 scripts/session_stamp.py --phase close \
    --marker "${stm_base}${issue}/status/session-stamp-start-change.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Evaluate the Done means here as the close's authoritative input.
python3 scripts/check_stop_condition.py \
    --manifest "<play-dir>/stop-condition.yaml" \
    --base "${stm_base}${issue}/" \
    --out "${stm_base}${issue}/status/stop-condition-start-change.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
(global + per-play `evidence.plays.start-change`; first match wins, absent ⇒ record).
When it resolves false, skip the write and record `evidence skipped (record=false)` in
the delivery report's pointer line. Otherwise fill the `evidence-file.md` slots (play
`start-change`, run_id `start-change-${ts}`, issue, started_at/completed_at, status,
artifacts produced: `issue.json`, `branch.json`, the STM workspace; step/scenario eval
results; checkpoint decisions from Step 2; commit reference `N/A — no commit step`; and
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
write to `$evidence_dest`. Do NOT hand-author the body.

**Step C2 — Render delivery report.** Also render the **Next** line: resolve this play in the Next-line map (`$next_map`, from `template-manager`) and emit `**Next:** /<command> — <why>.` (omitted when the mapped command is null), per `play-close.md`. Fill the `delivery-report.md` slots and output the
report to the user: `## start-change Delivered — #${issue}`, the Run Summary table, the
Pipeline Steps table built from the task DAG, the Artifacts Produced table, Next Steps
(the change is ready — run the build/implement play next; on the Plan path, title it `## start-change
Delivered — plan ${id}`, and its Next Steps say the plan's branch is ready and a story is started
with `/start-change <story>`), and a pointer to
`$evidence_dest`. Always emitted; never gated.

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

## Scenario Validation

The success scenarios and their evals (run in Step 6):

| Scenario | Persona | Eval |
|----------|---------|------|
| S1 — fresh start | developer | SCE-1 |
| S2 — existing issue | developer | SCE-2 |
| S3 — resume | developer | SCE-3 |
| S4 — worktree by config | developer | SCE-4 |
| S5 — first issue of a plan | developer | SCE-5 |
| S6 — plan already started | developer | SCE-6 |
| S7 — an issue added on main while the checkout is older | developer | SCE-7 |
| S8 — start a plan on its own | product owner | SCE-8 |
| S9 — start a plan again | product owner | SCE-9 |
| S10 — a wrong plan number | anyone | SCE-10 |
| S11 — nothing typed | developer | SCE-11 |
| S12 — any change on a plan's branch | developer | SCE-12 |
| S13 — a bug in a finished plan | developer | SCE-13 |

## Recovery

| For | Trigger | Direction | Handoff |
|-----|---------|-----------|---------|
| F1 | no issue resolved or created before branch work | create the issue from the work description via `track-issue`, then continue | autonomous |
| F2 | branch base is not the latest main (for an issue in a plan: not the plan branch with the latest main merged in), or current branch is `main` | bring the base up to date and re-cut the branch from it, or move off main | autonomous |
| F3 | worktree state does not match `start-change.worktree` | reconcile to config — remove the stray worktree, or create the missing one | autonomous |
| F4 | close check finds the STM workspace missing | run `scripts/init_stm.py` to initialize it before reporting done | autonomous |
| F5 | a second run would create a duplicate issue or branch | detect the existing issue and branch and resume them instead of creating new ones | autonomous |
| F6 | the close would report COMPLETED without the Done means held | evaluate the stop condition and surface the unmet clauses; the run closes HALTED until state is fixed | autonomous |
| F7 | `validate_issue.py` exits non-zero on the proposed or created issue record | a human fixes the title (present, ≥15 chars, tagged) and/or completes the work description, then re-runs; the play never creates over a failed validation | human |
| F8 | the mechanical branch work ran through an agent instead of `setup_branch.py` | route the cut/worktree/push through `setup_branch.py` and remove the agent dispatch | autonomous |
| F9 | the plan was not read by `resolve_plan.py` from git, an issue in a plan got a branch outside `plan/<plan-id>/`, a plan's branch was cut without main's plan file first, or a stale local plan branch was at risk | run `resolve_plan.py` and re-cut the branch with its names, the plan's branch through the plan tool; an issue in two plans, or `setup_branch.py` stopping on a stale copy with commits found nowhere else or on an origin it could not reach, stops for a person (fix the plan files, push or move the commits, or run again when origin can be reached) | human |
| F10 | a run started a change the person did not name: nothing was typed and it went on, or the change came from the branch name | stop with the one line naming what to give; take the change only from `resolve_args.py` | autonomous |
| F11 | `plan-start-check.json` names a problem: a story branch cut, an issue looked up, a commit on the plan's branch, main's plan file not In Progress on the branch, or a plan that should not start got a branch | delete a story branch that was cut and take a commit off the plan's branch; for a cut without main first, or a plan that should not have started, stop and name it for a person (the plan tool's `abort` undoes a cut that holds nothing else) | human |

## Pause and Resume

Steps run top to bottom. On entry, resolve the issue (from what was typed — `args.json` — then its status marker, or its
existing `feature/<issue>-*` or `plan/<plan-id>/<issue>-*` branch; never from the branch the session is on), 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 never duplicates the issue or
branch (C6, F5). A plan's start (`plan <id>`) does not resume: run again, it finds the plan's
branch and switches to it, changing nothing (S9).

## Compilation Metadata

| Field | Value |
|-------|-------|
| fingerprint | sha256:4e7a227766b93330ab01e1c10e3d5be4c5a8bd0dbcda2e345267395b9d4692cb (of `reference/ice.md`) |
| compiled_by | edit-play (#466 Batch A; #467 Batch C; #484 scripted chain; #731 plan branches; #779 plan read from git; #953 safe stale-branch delete; #989 the change is what is typed, and a plan starts on its own) |
| pipeline_position | start |
| workflow_structure | B (fast execution flow), two paths: a change, or a plan's start |
| stop_condition | stop-condition.yaml (D1–D4, a change) and stop-condition-plan.yaml (D5–D8, a plan's start), gate live at Step C0 |
| domain_agents | 0 |
| utility_agents | 1 (project-orchestrator — issue work + fresh-match judgment) |
| skills_reused | track-issue |
| scripts | 9 (init_stm.py, preflight.py, session_stamp.py, check_stop_condition.py, validate_issue.py, setup_branch.py, resolve_plan.py, resolve_args.py, check_plan_start.py) + test_start_change.py |
| tools | the plan tool's `plan_branch.py` (every cut of a plan's branch, main first) |
| step_evals | 12 (SE-1…SE-12) |
| scenario_evals | 13 (SCE-1…SCE-13) |
| recovery_entries | 11 (one per failure condition; 8 autonomous / 3 human) |

Files in this skill

  • SKILL.md31.4 KB
  • reference/ice.md16.7 KB
  • scripts/check_plan_start.py4.9 KB
  • scripts/check_stop_condition.py5 KB
  • scripts/init_stm.py2.1 KB
  • scripts/preflight.py7 KB
  • scripts/resolve_args.py4.6 KB
  • scripts/resolve_plan.py8 KB
  • scripts/session_stamp.py4.8 KB
  • scripts/setup_branch.py8.7 KB
  • scripts/test_start_change.py7.6 KB
  • scripts/validate_issue.py3 KB
  • stop-condition-plan.yaml1013 B
  • stop-condition.yaml810 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.