propose-change

Raise the current change for review — run a scope-and-quality self-review from the project's standards, push the branch, and open a pull request carrying that review and the issue reference. The secon

SKILL.md

GitHub

propose-change

Raise the current change for review: run a self-review on its scope and quality, push the branch, and open a pull request that carries that review — so the change is ready for check-change.

Pipeline position: end (second step of the end sequence: commit-change → propose-change → check-change → merge-change). The D2 pipeline-position rule injects this sequence into any play declared position: end. The plays are independent and invokable; ordering is enforced by each one's pre-flight, not by welding them together.

Compiled From

This play was compiled from the propose-change ICE (reference/ice.md) by edit-play (#466 Batch A, Level 3 rollout per ADR 025; #467 Batch C gate ruling). Intent defines constraints (C1–C9) and failure conditions (F1–F9); the expectation defines success scenarios (S1–S4), a Done means (D1–D3, baked to stop-condition.yaml), and one recovery entry per failure condition. This play is SINGLE-PASS: no loop, no iteration cap — the Done means is proven once, at the gated close. 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. The self-review is genuine judgment, so it goes to a utility agent (assess-pr). The mechanical push + PR-open is a fixed git/gh sequence, so it runs in the bundled submit_pr.py, not an agent (C9, #484). You never reason out git/gh inline in play prose.

Forbidden: running the mechanical push/PR-open through an agent dispatch (the #484 bug); ad-hoc gh/git in play prose; inlining the self-review rules (they are resolved from a standards file — C6).

Agent boundaries:

Agent Domain Skill it invokes Phases
repo-orchestrator Self-review checklist (judgment) assess-pr Execution (Step 1)

repo-orchestrator is a utility agent (the self-review judgment), exempt from the domain-agent budget. The push/PR-open is the bundled submit_pr.py — no agent (C9). This play uses zero domain agents.

Pre-flight

Check Constraint Action on Failure
Resolve working base + config (.garura/core/config.yaml) — Hard halt
On a feature branch (not main) C2 Hard halt
Working tree is clean (change is committed) C3 Hard halt
Platform CLI available (gh) C2 Hard halt
Resolve self-review rules file via standards_order (project override → base standards/rules/self-review.md) C6 Hard halt
Existing-PR check for the branch C5 Resume / update

Resolve the deterministic pre-flight facts with the bundled resolver — config tokens, branch, issue (from the branch name), on_default_branch, evidence_record:

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

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}{issue}/status/session-stamp-propose-change.json" \
    --cwd "$(pwd)" --branch "$(git branch --show-current)"

The resolver does NOT pick the self-review standards file (that stays with resolve_standard.py below — live precedence) and does NOT run the clean-tree or open-PR checks (live git/gh, kept in the table). Resolve the self-review rules path mechanically: read the standards_order base dirs from config (most-specific first) and run

python3 scripts/resolve_standard.py --file self-review.md --dir <most-specific> … --dir <base>

which returns the resolved path and an is_override flag — record both for the run (this is the C6 resolution; the play does not pick the file in prose).

Resume / update (C5, F5): if an open PR already exists for the branch, this run updates it (refreshes the self-review and body) instead of opening a second 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] Self-Review (scope + quality)   blockedBy: []
[T2] Confirm Raise                   blockedBy: [T1]
[T3] Push + Open/Update PR           blockedBy: [T2]
[T4] Scenario Validation             blockedBy: [T3]
[T5] Close                           blockedBy: [T4]

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 — Self-Review (scope + quality) · Owner: repo-orchestrator · Depends on: pre-flight The agent invokes assess-pr against the diff, driven by the rules file resolved at pre-flight, and writes the checklist to disk. The contract moves only paths:

{
  "task":    "run the scope+quality self-review using the resolved rules file",
  "inputs":  { "rules_file": "<resolved self-review.md path>",
               "issue": "<issue number>" },
  "outputs": { "self_review": "<working>/self-review.md",
               "resolved_rules": "<working>/resolved-rules.json" }
}

resolved-rules.json is the output of scripts/resolve_standard.py from pre-flight (the resolved path + is_override); the agent passes that path to assess-pr, it does not re-resolve. SE-1 (F1/C1): self-review.md exists and is produced before any PR is opened. SE-2 (F6/C6): resolved-rules.json names the project override when one exists under standards_order, else the base file — the rules were read from the standards file, not inlined in the play.

Phase: Approval

Step 2 — Confirm Raise · Owner: play · Depends on: Step 1 · Checkpoint (class: standard) Opening or updating a PR is outward-facing. The checkpoint is a config switch per standards/rules/gate-config.md (C8; first match wins: gates.plays.propose-change → gates.classes.standard → gates.default; absent ⇒ on) — per the #467 ruling gates.plays.propose-change is off, so the resolved default is the off path; flipping the config back to on restores the wait unchanged.

  • On — present the self-review and the PR draft (title, body, base = main, issue reference) and wait for a typed approval. Approve → continue; cancel → halt without pushing or opening.
  • Off — do not wait: record gate skipped by config (gates.plays.propose-change) as a Checkpoint Decisions row in the evidence and proceed. The skip is never silent.

Gate on or off, the human's blocking scan is replaced by a machine check, not dropped: Step 3 runs scripts/check_self_review.py BEFORE any push or PR-open, and a blocking finding halts to a human (C8, F8). Tree-clean (C3, pre-flight) and the issue reference (C4, SE-4) are the other two legs of the raise precondition — machine walls, never gated by this switch.

Phase: Execution

Step 3 — Push + Open/Update PR · Owner: play (script) · Depends on: Step 2 Pre-push self-review check (C8/F8) — gate on or off, always first:

python3 scripts/check_self_review.py --self-review <working>/self-review.md

Exit 0 (clean) → proceed. Non-zero → HALT to a human before any push or PR-open: surface the blocking findings the script printed, and do not push or open until they are fixed and the check passes (REC8). Record the executed command and its exit code for the evidence file.

Then run the bundled submit_pr.py — it pushes the branch and opens (or updates, C5) a PR against main carrying the pre-rendered body, and writes pr.json (via platform_adapter.py). No agent (C9). Assemble the body first (the self-review embedded + the issue reference), and commit the run artifacts BEFORE the push so the tree stays clean and the PR carries the record (C7): (1) render the PR body to <working>/pr-body.md; (2) git add the issue's context dir and commit chore(stm): record propose-change run artifacts (#<issue>) (self-review + resolved-rules); (3) run the script:

python3 scripts/submit_pr.py --config .garura/core/config.yaml \
    --title "<conventional title (#<issue>)>" --base main \
    --body-file "<working>/pr-body.md" --out "<working>/pr.json"

then (4) commit pr.json under the same message — never an amend — and push, so the PR carries the play's full record. SE-6 (F3/C3): the working tree was clean (no uncommitted changes) immediately before the push — nothing was left behind. SE-3 (F2/C2): after the step, local HEAD matches the pushed remote branch and a PR is open against main for the branch. SE-4 (F4/C4): the PR body references the issue (#<issue>). SE-5 (F5/C5): the open-PR count for the branch is exactly one — a pre-existing PR was updated, not duplicated. SE-8 (F8/C8): check_self_review.py was executed BEFORE the push, and its recorded exit code is 0 — no push or PR-open happened while the self-review carried a blocking finding. SE-9 (F9/C9): the push + PR-open ran as the bundled submit_pr.py (via platform_adapter.py), not an agent dispatch — no repo-orchestrator/open-pr was invoked for the push/PR work.

Phase: Scenario Validation

Step 4 — Scenario Evals · Owner: play · Depends on: Step 3

  • SCE-1 (S1 — developer, clean change): a PR exists for the branch whose body carries the self-review checklist and references the issue; the run artifacts were committed before the push; the stop-condition verdict reads held.
  • SCE-2 (S2 — developer, project override): when the project supplies its own rules file, resolved-rules.json shows the override was used.
  • SCE-3 (S3 — developer, re-raise): a second run left the open-PR count unchanged and reused the same PR number.
  • SCE-4 (S4 — developer, dirty tree): with uncommitted changes, the run halted at pre-flight — no push and no PR were made.

Phase: Evidence & Close

Step 5 — Close · Owner: play · Depends on: Step 4 Run the Standard Play Close. Evidence recording is play-only and config-gated per the D1 evidence rule (standards/rules/evidence-recording.md). The close is GATED (the stop-condition step below, #464): the verdict decides COMPLETED vs HALTED. SE-7 (F7/C7): the stop-condition verdict read held before COMPLETED was stamped; the run artifacts are committed and on the remote branch — the PR carries them.

# --- 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/propose-change/"   ; slug="#${issue}"

# Session identity stamp (#463) — close phase; start phase ran at pre-flight
session_stamp=$(python3 scripts/session_stamp.py --phase close \
    --marker "${stm_base}${issue}/status/session-stamp-propose-change.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. Single-pass play: the Done means is proven here, once.
python3 scripts/check_stop_condition.py \
    --manifest "<play-dir>/stop-condition.yaml" \
    --base "${stm_base}${issue}/" \
    --out "${stm_base}${issue}/status/stop-condition-propose-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.propose-change; 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 propose-change, run_id propose-change-${ts}, issue, started_at/completed_at, status per C0, exit_reason, artifacts produced: self-review.md, resolved-rules.json, pr.json (the PR number/URL), the stop-condition verdict; step/scenario eval results; checkpoint decision from Step 2 (or its config-skip row); commit reference = the run-artifact commits from Step 3; 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 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: ## propose-change Delivered — #${issue}, the Run Summary table, the Pipeline Steps table from the task DAG, the Artifacts Produced table (incl. the PR URL), Next Steps (run check-change on the PR), and a pointer to $evidence_dest. Always emitted; never gated.

# --- end Standard Play Close ---

Scenario Validation

Scenario Persona Eval
S1 — clean change developer SCE-1
S2 — project override developer SCE-2
S3 — re-raise developer SCE-3
S4 — dirty tree developer SCE-4

Recovery

For Trigger Direction Handoff
F1 a PR would open without the self-review run the self-review from the resolved rules file and attach it before opening autonomous
F2 the push or PR-open failed retry the push/open and surface the platform error autonomous
F3 uncommitted changes at raise time halt and have the change committed (e.g. via commit-change) before raising human
F4 the PR has no issue reference add the issue reference to the PR autonomous
F5 a second run would open a duplicate PR detect the existing PR for the branch and update it instead autonomous
F6 the self-review ran on hardcoded rules or ignored a project override re-resolve the rules file via standards_order (project override → base) and re-run the self-review autonomous
F7 the close would stamp COMPLETED without the stop-condition verdict held evaluate the stop condition, surface the unmet clauses, and close HALTED (stop_condition_unmet) until they are fixed — an unevaluable verdict is never a pass autonomous
F8 a push or PR-open happened (or would happen) while the self-review carried a blocking finding halt to a human — fix the blocking findings, then re-run so check_self_review.py passes before any push or PR-open human
F9 the mechanical push or PR-open ran through an agent instead of submit_pr.py route it through submit_pr.py (via platform_adapter.py) and remove the agent dispatch autonomous

Pause and Resume

Steps run top to bottom. On entry, resolve the issue from the branch name, check the status marker and whether an open PR already exists for the branch, skip completed steps, reset any in-progress step to pending, and continue. A re-run with an existing PR updates it rather than opening a new one (C5, F5).

Compilation Metadata

Field Value
fingerprint sha256:51f2edb37f2b3aa6399486127127c5b9238a19b47c65b335e0717c064f194871 (of reference/ice.md)
compiled_by edit-play (#466 Batch A; #467 Batch C; #484 scripted chain)
pipeline_position end
workflow_structure B (fast execution flow, single-pass — no loop, no cap; gated close)
stop_condition stop-condition.yaml (D1–D3), gate live at Step C0
domain_agents 0
utility_agents 1 (repo-orchestrator — self-review judgment, Step 1 only)
skills_reused assess-pr
standards_consumed standards/rules/self-review.md (overrideable per project); standards/rules/gate-config.md (Step 2 switch)
scripts 7 (resolve_standard.py — standards-file precedence; preflight.py — pre-flight facts; session_stamp.py — session identity stamp; check_stop_condition.py — Done-means gate; check_self_review.py — pre-push blocking-finding check; platform_adapter.py — code-host adapter; submit_pr.py — push + PR-open)
step_evals 9 (SE-1…SE-9)
scenario_evals 4 (SCE-1…SCE-4)
recovery_entries 9 (one per failure condition; 7 autonomous / 2 human)

Recompile note (#484, scripted chain)

Intent change via reference/ice.md → recompile. Added C9 (mechanical push + PR-open runs in the bundled submit_pr.py, never agent dispatch), F9, REC9, SE-9. Step 3 no longer dispatches repo-orchestrator → open-pr; it runs submit_pr.py via platform_adapter.py (canonical copies in create-play/references/, stamped here). The self-review (Step 1) keeps repo-orchestrator → assess-pr — it is genuine judgment, not mechanical. Scripts 5 → 7; skills_reused drops open-pr. Guarantees (self-review attached, tree clean before push, issue referenced, no duplicate PR, blocker check) unchanged. Fingerprint recomputed over the edited ICE.

Direct-edit deviation note (#434, harness-led sweep)

Non-intent edit: the C6 self-review file resolution was moved from prose into a called script (scripts/resolve_standard.py, pure filesystem precedence — no git/gh). No constraint/failure/scenario/eval text changed; reference/ice.md and the fingerprint are unchanged. create-play step 3 is taught the same discipline so a rebuild reproduces it.

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

Also non-intent: pre-flight resolution moved from orchestrator inference to the bundled scripts/preflight.py (config/branch/issue/changeset facts as JSON; the play keeps the halt policy). It does NOT resolve the self-review standards file (that stays with resolve_standard.py) nor the clean-tree/open-PR checks (live git/gh). The script is the canonical resolver stamped from create-play/references/preflight.py; a rebuild reproduces it (create-play step 4). reference/ice.md and the fingerprint are unchanged. Direct edit — recompiling would clobber this play's hand-added scripts, so the wiring is hand-added here.

Recompile note (#467 Batch C, chain gates off)

Intent change via reference/ice.md → edit-play: the Confirm Raise checkpoint gained C8/F8 — the gate resolves per gate-config (per-play gates.plays.propose-change now off; skips recorded), and gate on or off the play runs the bundled scripts/check_self_review.py on self-review.md BEFORE any push or PR-open, halting to a human on any blocking finding (non-zero exit). Tree-clean (C3) and the issue reference (C4) are named as the other two legs of the raise precondition. Checkpoint machinery is kept, not removed: flipping the config back to on restores the human wait unchanged. check_self_review.py is stamped from the canonical create-play/references/check_self_review.py. The fingerprint reflects the updated ICE.

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.