Commit all uncommitted work on the feature branch, grouped by concern, with conventional messages that reference the issue — leaving a clean tree ready to raise. Commits only; does not push. The first step of the end sequence in the ProductOS change pipeline. Use when work is done and needs committing before propose-change.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add kapilvirenahuja/garura --skill commit-change --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Commit Change?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/kapilvirenahuja-commit-change)More formats (shields.io, HTML) on the badges page.
---
name: commit-change
position: end
description: 'Commit all uncommitted work on the feature branch, grouped by concern, with conventional messages that reference the issue — leaving a clean tree ready to raise. Commits only; does not push. The first step of the end sequence in the ProductOS change pipeline. Use when work is done and needs committing before propose-change.'
user-invocable: true
---
# commit-change
Commit all uncommitted work on the feature branch, grouped by concern, with conventional
messages that reference the tracked issue — leaving a clean tree ready to raise. This play
**commits only; it does not push** (propose-change pushes when it opens the PR).
**Pipeline position: end.** commit-change is the first step of the end sequence
(commit-change → propose-change → review-change → merge-change) that the D2 pipeline-position
rule injects into any play declared `position: end`. Positions are start and end only —
there is no middle.
**The pilot (#465).** This is the first play compiled on the Level 3 model (ADR 025):
the skeleton — pre-flight, one loop, conditional checkpoint, gated close — is deterministic
and guaranteed; the interior of the loop is the agent's, bounded by four walls: the baked
stop condition (`stop-condition.yaml`), the bundled scripts as the checker, an iteration
cap of 5, and the run's evidence. The play ends by PROVING its Done means (C9), never by a
step list running out.
## Compiled From
This play was compiled from the commit-change ICE (`reference/ice.md`) by play-editor
(#465, Level 3 pilot recompile; #467 Batch C gate-off recompile). Intent defines
constraints (C1–C9) and failure conditions (F1–F9); the expectation defines success
scenarios (S1–S6), 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 play-editor.
Do NOT edit this file manually — it is a compiled artifact.
## Role
You are the orchestrator. You own the skeleton — pre-flight, the loop's walls, the
checkpoint policy, the gated close — and the agent owns the interior of each round.
Mechanical work runs as bundled scripts (C8): the changeset scan and triviality
classification (`scripts/analyze_changeset.py`), commit execution over the decided plan
(`scripts/execute_commits.py`), and the stop-condition evaluation
(`scripts/check_stop_condition.py`). Inference is reserved for the one genuine judgment —
grouping a multi-concern changeset — delegated to `repo-orchestrator` (invoking
`analyze-changes`) via a JSON contract over files on disk. Issue resolution, when not
derivable from the branch, goes to `project-orchestrator` (`resolve-issues`,
`manage-issue`). **You never push** — that is propose-change's job (C7).
**Forbidden:** ad-hoc `git`/`gh` commands reasoned out inline — git runs only inside the
bundled scripts or the dispatched skills; spawning an analyze agent for a changeset the
script classified trivial (C8/F7); any push (`git push`, `submit-pr`, or equivalent — the
execution script contains no push path); closing COMPLETED without the stop condition
held (C9); exceeding the iteration cap (F8).
**Agent boundaries:**
| Agent | Domain | Skill it invokes | Phases | Dispatched when |
|-------|--------|------------------|--------|-----------------|
| `repo-orchestrator` | Multi-concern grouping judgment | `analyze-changes` | Loop | `analyze_changeset.py` reports `needs_judgment: true` |
| `project-orchestrator` | Resolve change groups to the tracked issue | `resolve-issues`, `manage-issue` | Loop | issue not derivable from the branch |
Both are **utility agents**, exempt from the domain-agent budget. This play uses **zero
domain agents**, and on a trivial changeset it spawns **zero agents at all** (S5).
**The plan contract (C3, #465 defect 1).** The plan (`analysis.yaml`) carries each group's
subject BARE — no type prefix, no scope, no issue reference; those live in the group's
separate `commit_type` / `scope` / `issue` fields. The executor renders the conventional
message and defensively normalizes a subject that arrives pre-formatted (strips a doubled
prefix or trailing issue ref) instead of doubling it.
## Pre-flight
| Check | Constraint | Action on Failure |
|-------|-----------|-------------------|
| Resolve working base + config (`.garura/core/config.yaml`) | — | Hard halt |
| Branch guard (not on `main`/default) | C1 | Hard halt |
| Changes exist (`git status --porcelain` non-empty) | C5 | Graceful exit |
| Sensitive-file scan (loop's scan script, `analyze_changeset.py` patterns) | C6 | Hard block |
| Issue resolvable (from the `feature/<issue>-*` branch, else open issues) | C3 | Hard halt |
Resolve the pre-flight facts mechanically with the bundled resolver — do not derive them by
inference. The orchestrator captures the two live reads and hands them to the script:
```
git branch --show-current # current branch
git status --porcelain > <working>/porcelain.txt # changeset
python3 scripts/preflight.py --play commit-change \
--config .garura/core/config.yaml \
--branch "<current branch>" \
--porcelain-file <working>/porcelain.txt
```
It returns one JSON object of facts: `stm_base`, `ltm_project_target`, `evidence_record`,
`platform`, `branch`, `issue`, `on_default_branch`, `changes_present`. The orchestrator reads
those and applies the **policy** in the table above (the script resolves facts, the play
decides halts): `on_default_branch == true` → hard halt (C1); `changes_present == false` →
graceful exit (C5); a non-null `issue` short-circuits issue resolution
(`auto_issue_resolved = true`) and skips the checkpoint (C3). The sensitive-file scan runs
inside the loop's scan script (same porcelain file), not the resolver.
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-commit-change.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
```
**Resume check:** if `{stm_base}/{issue}/status/commit-change.json` exists, resume — the
loop re-enters at the next round with the recorded round count; the cap counts all rounds
across resumes.
## 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. The DAG is the skeleton:
the loop is ONE task, however many rounds it takes (C9 — the interior is the agent's).
```
[T1] Commit Loop (≤5 rounds) blockedBy: []
[T2] Scenario Validation blockedBy: [T1]
[T3] Close (stop-condition gated) blockedBy: [T2]
```
Mark each task in-progress before its phase and completed right after its eval passes. On
resume, the loop task re-enters with its round counter.
## Workflow
### Phase: The Loop
**Loop — Commit until Done means holds** · Owner: play (scripts) + agent on judgment ·
Depends on: pre-flight · **Cap: 5 rounds (C9/F8)**
Each round the agent decides its own moves toward the goal — *every change committed or
excluded-with-reason, tree clean, nothing pushed* — using the bundled scripts as its hands.
A round typically:
1. **Scan** — always the script first:
```
python3 scripts/analyze_changeset.py \
--porcelain-file <working>/porcelain.txt \
--issue {issue} --repo-root . \
--out <working>/analysis.yaml
```
Policy on its summary: `sensitive` (blocking) non-empty → **hard block** (C6/F5),
human recovery (REC5) — the block is a machine wall, never gated by the checkpoint
switch. `warnings` non-empty (content-pattern matches in prose/standards
artifacts — pattern carriers, #438) → never a block; surfaced at the checkpoint when
the gate is on, carried into the delivery report's Run Summary when it is off (F9).
`needs_judgment: false` → the script's plan IS the plan; a `subject_source: script-draft`
subject may be reworded in place (one line, no agent); **no agent is dispatched (C8)**.
`needs_judgment: true` → dispatch `repo-orchestrator` → `analyze-changes` for the
grouping judgment, standard JSON contract (subjects BARE per the plan contract):
```
{
"task": "group the changeset by concern; flag sensitive/risky files",
"inputs": { "scan": "<working>/analysis.yaml" },
"outputs": { "analysis": "<working>/analysis.yaml" }
}
```
2. **Resolve issue** — *skip when derived from the branch (`auto_issue_resolved`)*;
otherwise `project-orchestrator` invokes `manage-issue` + `resolve-issues` and writes
`issue-mappings.yaml`.
3. **Checkpoint** — *only when the skip condition fails* (a sensitive flag, scanner
warnings, or low-confidence mapping) (class: standard). When it fires, resolve the
gate switch per `standards/rules/gate-config.md` FIRST:
- **off** (this project: `gates.plays.commit-change: off`) → do not wait; record
`gate skipped by config (gates.plays.commit-change)` as a Checkpoint Decisions row
in evidence and proceed to execute. The off path drops nothing silently (C9/F9):
scanner **warnings** are carried into the delivery report's Run Summary (a visible
surface); a **low-confidence mapping** proceeds with the mapping recorded as
low-confidence in `issue-mappings.yaml` and the run record — never silently
upgraded. The **sensitive-file block** is untouched by the switch — it already
hard-halted at the scan (a machine wall, never a gate).
- **on** → present the brief — groups, mapping + confidence, risk flags, warnings —
and wait for typed approval. Approve → execute; cancel → halt.
4. **Execute** — the decided plan, mechanically, run-state finalization included
(#465 defects 2+3 — ignored paths become recorded exclusions; the play's own run
artifacts land as the final chore group):
```
python3 scripts/execute_commits.py \
--analysis <working>/analysis.yaml \
--issue {issue} \
--out <working>/commits.yaml \
--run-state-dir <working>
```
5. **Prove or go again** — evaluate the stop condition:
```
python3 scripts/check_stop_condition.py \
--manifest <play-dir>/stop-condition.yaml \
--base "{stm_base}{issue}/" \
--out "{stm_base}{issue}/status/stop-condition-commit-change.yaml"
```
**Held (exit 0)** → leave the loop. **Unmet (exit 1)** → the verdict's unmet clauses
are the next round's work list (fresh porcelain, plan the remainder); increment the
round counter and sync it to the status marker. **Round 5 still unmet** → halt HALTED,
`exit_reason: loop_cap_exhausted`, verdict recorded (F8/REC8 — human).
**Loop evals (each round's exit is checked, the loop's exit is proven):**
- **SE-1 (F1/C2):** each change group in `analysis.yaml` holds files of a single concern.
- **SE-2 (F2/C3):** each commit message is conventional `type(scope): subject (#issue)`
with NO doubled prefix and exactly one issue reference — the executor normalized any
pre-formatted subject.
- **SE-3 (F3/C4):** `commits.yaml` reads `leftover_count: 0` and `tree_clean: true`, or
the loop is still running / halted with the verdict naming the leftovers. Ignored paths
appear as recorded exclusions, never as add-failures.
- **SE-4 (F4/C1):** the commits were made on the feature branch, not `main`.
- **SE-5 (F5/C6):** no file flagged BLOCKING by the scan is included; a blocking flag
halts the run. Prose/standards content matches are surfaced warnings, not blocks (#438).
- **SE-6 (F6/C7):** no push occurred — `commits.yaml` reads `pushed: false` and the remote
tip is unchanged by this run.
- **SE-7 (F7/C8):** the dispatch matches the classification — `needs_judgment: false` and
zero analyze-agent spawns, or `true` and exactly one (per round). A trivial-classified
analysis whose group spans unrelated file sets triggers REC7.
- **SE-8 (F8/C9):** the loop exited by a held stop condition, or halted at the cap with
the verdict recorded — the round counter never exceeds 5 and a cap-halt never closes
COMPLETED.
- **SE-9 (F9/C9):** every scanner warning the run produced is visible somewhere — at a
fired checkpoint (gate on) or in the delivery report's Run Summary Scanner Warnings
row (gate off); a run with warnings and neither surface fails this eval (REC9
re-renders the report).
### Phase: Scenario Validation
**Scenario Evals** · Owner: play · Depends on: the loop
- **SCE-1 (S1 — developer, multi-concern):** each concern is a separate conventional
commit referencing the issue with no doubled prefix, the run artifacts are committed,
the tree is clean, and nothing was pushed.
- **SCE-2 (S2 — developer, nothing to commit):** a clean tree caused a graceful exit at
pre-flight with no commit created.
- **SCE-3 (S3 — reviewer, log readability):** every commit follows conventional format with
an issue reference, reviewable from the log alone.
- **SCE-4 (S4 — developer, sensitive file):** a sensitive file blocked the run
with no commit created.
- **SCE-5 (S5 — developer, trivial changeset):** a single-concern changeset ran entirely
through the bundled scripts — plan and commits script-emitted, zero analyze/commit agent
dispatches, same C2–C4 guarantees as the agent path.
- **SCE-6 (S6 — developer, loop convergence):** an unmet round fed its verdict to the next
round as the work list, and the run converged (verdict held) or halted with the unmet
clauses named and rounds ≤ 5.
### Phase: Evidence & Close
**Close** · Owner: play · Depends on: Scenario Validation
Run the Standard Play Close. Evidence recording is play-only and config-gated per the D1
evidence rule (`standards/rules/evidence-recording.md`).
```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/commit-change/" ; slug="#${issue}"
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}${issue}/status/session-stamp-commit-change.json")
# Stop-condition gate (#464) — Step C0: this play carries a baked manifest, so the
# gate is LIVE. The loop already wrote the verdict; re-read it 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-commit-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` (or
`loop_cap_exhausted` when the loop capped) 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.commit-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 `commit-change`, run_id
`commit-change-${ts}`, issue, started_at/completed_at, status per C0, exit_reason;
artifacts produced: `analysis.yaml`, `issue-mappings.yaml`, `commits.yaml`, the
stop-condition verdict; step/scenario eval results; checkpoint decisions from the loop's
checkpoint rounds; commit reference = the created commit SHAs incl. the run-state commit;
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:
`## commit-change Delivered — #${issue}`, the Run Summary table (incl. rounds used, the
stop-condition verdict, and a **Scanner Warnings** row — every warning the scan produced,
carried here whenever the checkpoint did not fire or was skipped by config; `none` when
the scan was clean (F9/SE-9)), the Pipeline Steps table from the task DAG, the Artifacts Produced
table (the commits created), Next Steps (run propose-change to push and open the PR), and a
pointer to `$evidence_dest`. Always emitted; never gated.
```bash
# --- end Standard Play Close ---
```
## Scenario Validation
| Scenario | Persona | Eval |
|----------|---------|------|
| S1 — multi-concern | developer | SCE-1 |
| S2 — nothing to commit | developer | SCE-2 |
| S3 — log readability | reviewer | SCE-3 |
| S4 — sensitive file | developer | SCE-4 |
| S5 — trivial changeset, scripts only | developer | SCE-5 |
| S6 — loop convergence | developer | SCE-6 |
## Recovery
| For | Trigger | Direction | Handoff |
|-----|---------|-----------|---------|
| F1 | a commit covers more than one concern | regroup so each commit holds a single concern; re-stage by group and recommit (a loop round) | autonomous |
| F2 | a commit subject is generic or missing the issue reference | rewrite the subject to describe the change and add the issue reference | autonomous |
| F3 | changed files remain uncommitted with no exclusion reason | the unmet verdict is the next round's work list — commit the remainder or record the exclusion | autonomous |
| F4 | the current branch is main | halt; move the work to a feature branch (start-change) before committing | human |
| F5 | a sensitive file is staged | unstage the sensitive file and confirm it is excluded before committing | human |
| F6 | the play attempted a push | this play commits only — drop the push; leave pushing to propose-change | autonomous |
| F7 | the script's triviality classification disagrees with the changeset | discard the fast-path analysis and re-run the scan through the analyze agent (or record a needless dispatch); the agent's grouping wins | autonomous |
| F8 | the loop hit its cap with the Done means unmet | halt HALTED (`loop_cap_exhausted`) with the verdict recorded; a human inspects the unmet clauses and fixes state or knowingly raises the cap | human |
| F9 | scan warnings exist but no checkpoint fired and the report carries no Scanner Warnings row | re-render the delivery report with the warnings carried into the Run Summary before closing | autonomous |
## Pause and Resume
On entry, resolve the issue from the branch name, check the status marker, and re-enter
the loop at the recorded round count — the cap counts all rounds across resumes. A fresh
start with no marker begins at round 1 and creates the marker.
## Compilation Metadata
| Field | Value |
|-------|-------|
| fingerprint | sha256:5e3b5afb2b9b097c99d610feda8d9b8bb749bf3de04b9c5583c3471995bfe95d (of `reference/ice.md`) |
| compiled_by | play-editor (#465, Level 3 pilot recompile per ADR 025; #467 Batch C) |
| pipeline_position | end |
| workflow_structure | loop (skeleton: pre-flight → loop ≤5 → validation → gated close) |
| stop_condition | stop-condition.yaml (D1–D4), gate live at Step C0 |
| iteration_cap | 5 |
| domain_agents | 0 |
| utility_agents | 2 (repo-orchestrator — judgment path only; project-orchestrator) |
| skills_reused | analyze-changes (judgment path), resolve-issues, manage-issue |
| scripts | 5 (preflight.py, analyze_changeset.py, execute_commits.py, check_stop_condition.py, session_stamp.py) |
| step_evals | 9 (SE-1…SE-9) |
| scenario_evals | 6 (SCE-1…SCE-6) |
| recovery_entries | 9 (one per failure condition; 6 autonomous / 3 human) |
## Compiled note (#484, scripted chain — already compliant)
commit-change already embodies the #484 tool-first rule and needs no rewiring: C8 mandates
"mechanical work runs as bundled scripts, not model inference" — the changeset scan
(`analyze_changeset.py`), a trivial changeset's whole analysis, and commit execution
(`execute_commits.py`) all run as scripts, and an agent is dispatched ONLY when the script
classifies the changeset multi-concern, i.e. when grouping genuinely requires judgment. That
is exactly #484's principle (mechanical git/gh in scripts; agents only for judgment). Unlike
the four sibling chain plays, no operation here was an agent wrapper over a fixed git
sequence, so no ICE constraint changed and the fingerprint stands. This note records the
audit; the play is unchanged.
## Compiled note (#467 Batch C)
The loop's conditional checkpoint went **off by config** for this project
(`gates.plays.commit-change: off`) with its replacement visibility checks in the same
change: scanner warnings are carried into the delivery report's Run Summary (F9, SE-9,
REC9 — never silently dropped); a low-confidence mapping proceeds recorded as
low-confidence, never silently upgraded; the sensitive-file BLOCK stays a hard halt at
the scan, untouched by the switch. 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. No new scripts. Owned by the compiled
play (ICE recompile, not a direct edit).
## Compiled note (#438, absorbed on recompile)
`analyze_changeset.py`'s secret scanner distinguishes artifact types: content-pattern
matches in prose/standards artifacts (pattern carriers by design) are non-blocking warnings
surfaced at the checkpoint; filename matches and content matches in runtime/config paths
hard-block. C6's guarantee is unchanged: actual secrets never commit. Formerly a direct-edit
deviation note; owned by the compiled play since the #465 recompile.
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!