start-change

Open a unit of work cleanly — resolve or create the tracked issue, cut 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

SKILL.md

GitHub

start-change

Open a unit of work cleanly: ensure a tracked issue, a feature branch 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.

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). Intent defines constraints (C1–C8) and failure conditions (F1–F8); the expectation defines success scenarios (S1–S4), a Done means (D1–D4, baked to stop-condition.yaml), and one recovery entry per failure condition. To modify this play, update reference/ice.md and recompile with edit-play. Do NOT edit this file manually — it is a compiled artifact.

Role

You are the orchestrator. You own the 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.

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 — no agent (C8). 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
Read start-change.worktree flag (default false) C3 —
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), branch, issue (null when starting fresh), 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.

Resume check (C6, F5): if an issue is already known (passed in, or a status marker / existing feature/<issue>-* branch is found), 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.

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.

[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]

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": "<optional, if provided>" },
  "outputs": { "issue": "<working>/issue.json" }
}

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 Run the bundled script — it pulls main to its latest origin tip, cuts feature/<issue_number>-<slug> from it (or switches to it 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 "feature/<issue_number>-<slug>" --base main \
    $( [ "<start-change.worktree>" = "true" ] && echo --worktree ) \
    --out "<working>/branch.json"

branch.json records { branch_name, base_sha, on_default_branch: false, worktree_path|null }. SE-2 (F2/C2): current branch matches feature/<issue_number>-*, is not main, and branch.json.base_sha equals the current origin/main tip. 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>

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.

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.
  • 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.

# --- 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}"

# 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>. 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 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), and a pointer to $evidence_dest. Always emitted; never gated.

# --- 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

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, or current branch is main pull main to latest 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

Pause and Resume

Steps run top to bottom. On entry, resolve the issue (from input, status marker, or an existing feature/<issue>-* branch), 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).

Compilation Metadata

Field Value
fingerprint sha256:57522cb5f2deebabeee2f4b5b3e3651ed5f95beafa98b9cf57b7812ce1c884b4 (of reference/ice.md)
compiled_by edit-play (#466 Batch A; #467 Batch C; #484 scripted chain)
pipeline_position start
workflow_structure B (fast execution flow)
stop_condition stop-condition.yaml (D1–D4), gate live at Step C0
domain_agents 0
utility_agents 1 (project-orchestrator — issue work + fresh-match judgment)
skills_reused track-issue
scripts 6 (init_stm.py, preflight.py, session_stamp.py, check_stop_condition.py, validate_issue.py, setup_branch.py)
step_evals 9 (SE-1…SE-9)
scenario_evals 4 (SCE-1…SCE-4)
recovery_entries 8 (one per failure condition; 7 autonomous / 1 human)

Recompile note (#484, scripted chain)

Intent change via reference/ice.md → recompile. Reworded C5 (issue work via track-issue; branch work is not a skill/agent) and added C8 (mechanical branch work runs in the bundled setup_branch.py, never agent dispatch), F8, REC8, SE-9. Step 4 no longer dispatches repo-orchestrator → cut-branch; it runs setup_branch.py (canonical copy in create-play/references/, stamped here). Issue work keeps project-orchestrator for the fresh-issue match (genuine judgment). Utility agents 2 → 1; scripts 5 → 6. Guarantees (issue-anchored, branch off latest main, worktree per config, resume-safe, Done-means proven) unchanged. Fingerprint recomputed over the edited ICE.

Direct-edit deviation note (#434, pre-flight resolver)

Pre-flight resolution moved from orchestrator inference to the bundled scripts/preflight.py (harness-led: config/branch/issue/worktree/changeset resolution is a script returning JSON facts; the play keeps only the halt policy; the live git-repo and gh checks stay in the table). The script is the canonical resolver stamped from create-play/references/preflight.py; a rebuild reproduces it (create-play step 4). Non-intent change — no constraint, failure, scenario, eval, or reference/ice.md touched, so the fingerprint stands and no recompile is required. Direct edit; no recompile needed.

Compiled note (#467 Batch C)

The confirm-new-issue gate went off by config for this project (gates.plays.start-change: off) in the same change that added its replacement machine check: scripts/validate_issue.py (canonical copy: create-play/references/validate_issue.py) runs before any issue create, gate on or off, and a non-zero exit hard-halts to a human (C1, F7, SE-8, REC7). The checkpoint machinery is KEPT — the switch resolves per standards/rules/gate-config.md; flipping the config back to on restores the wait; skips are always recorded in evidence. Owned by the compiled play (ICE recompile, not a direct edit).

Direct-edit deviation note (#463, session identity stamp)

Non-intent change: the Standard Play Close gained the session identity stamp — scripts/session_stamp.py (canonical copy: create-play/references/session_stamp.py) runs --phase start at pre-flight and --phase close in the close block; the evidence frontmatter carries session_id / ledger_file / ledger_start_offset / ledger_end_offset. Source of truth: core/components/plays/_shared/play-close.md; create-play emits the same lines so a rebuild converges. No constraint, failure, scenario, or eval changed; the fingerprint stands.