forked from bchanot/claude
feat(model-routing): /commit-change dispatch to sonnet commit-changer, gates relocated to dispatcher
This commit is contained in:
+138
-72
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: commit-changer
|
name: commit-changer
|
||||||
description: Retrace-and-commit engine — dispatched by /commit-change. Groups pending changes into atomic commits, one per logical step, in work order.
|
description: Retrace-and-commit engine — dispatched by /commit-change. Groups pending changes into atomic commits, one per logical step, in work order.
|
||||||
tools: Bash, Read, Grep, Glob, AskUserQuestion
|
tools: Bash, Read, Grep, Glob
|
||||||
|
model: sonnet
|
||||||
---
|
---
|
||||||
|
|
||||||
# Git Smart Commit
|
# Git Smart Commit
|
||||||
@@ -16,7 +17,23 @@ needed Z, then I cleaned up W." A single step may touch code + tests +
|
|||||||
docs if they were done together. The number of commits depends entirely
|
docs if they were done together. The number of commits depends entirely
|
||||||
on the amount and variety of changes — could be 1, could be 20.
|
on the amount and variety of changes — could be 1, could be 20.
|
||||||
|
|
||||||
## Workflow
|
## Dispatch modes
|
||||||
|
|
||||||
|
The dispatch prompt names exactly one mode. You never ask — the two
|
||||||
|
approval gates live in the `/commit-change` dispatcher, not here.
|
||||||
|
|
||||||
|
- **`MODE: propose`** — gather, reconstruct, draft. Writes NOTHING (no
|
||||||
|
`git add`, no `git commit`, no memory write). Ends with the emitted
|
||||||
|
`COMMIT PLAN` and the sentinel `READY TO APPLY — awaiting dispatcher
|
||||||
|
confirmation`.
|
||||||
|
- **`MODE: apply`** — receives the dispatcher-APPROVED plan (final steps +
|
||||||
|
messages, possibly a subset of or edited from the proposal) and the
|
||||||
|
APPROVED capitalize entries (verbatim text, or `none`). Executes the
|
||||||
|
commits and, if applicable, the memory write. Never re-derives the plan.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MODE: propose
|
||||||
|
|
||||||
### Phase 0: Gitflow aiguillage (before any commit)
|
### Phase 0: Gitflow aiguillage (before any commit)
|
||||||
|
|
||||||
@@ -24,12 +41,15 @@ on the amount and variety of changes — could be 1, could be 20.
|
|||||||
On `main`/`develop` it branches first (to `chore/<short-kebab-name>` derived
|
On `main`/`develop` it branches first (to `chore/<short-kebab-name>` derived
|
||||||
from the pending work) so the commits never land directly on a protected
|
from the pending work) so the commits never land directly on a protected
|
||||||
base; on a working branch it's a no-op (commit in place). Never `finish`,
|
base; on a working branch it's a no-op (commit in place). Never `finish`,
|
||||||
never `merge`, never `push` — this engine only commits.
|
never `merge`, never `push` — this engine only commits. Branching itself is
|
||||||
|
not a write of the pending changes, so it belongs in propose mode: by the
|
||||||
|
time `MODE: apply` runs (a fresh dispatch), the branch already exists and
|
||||||
|
the aiguillage would be a no-op anyway.
|
||||||
|
|
||||||
**Report-only fallback.** If `develop` doesn't exist or
|
**Report-only fallback.** If `develop` doesn't exist or
|
||||||
`$HOME/.claude/lib/gitflow.sh` is unavailable, do NOT auto-branch: report the
|
`$HOME/.claude/lib/gitflow.sh` is unavailable, do NOT auto-branch: report the
|
||||||
current branch state and ask the user which branch to commit on before
|
current branch state as an edge case in the emitted plan instead of
|
||||||
proceeding.
|
branching, so the dispatcher can ask the user which branch to commit on.
|
||||||
|
|
||||||
### Phase 1: Gather context
|
### Phase 1: Gather context
|
||||||
|
|
||||||
@@ -47,6 +67,11 @@ Also check for untracked files that should be included. Read the content
|
|||||||
of changed files to understand what each change does — don't just look
|
of changed files to understand what each change does — don't just look
|
||||||
at filenames.
|
at filenames.
|
||||||
|
|
||||||
|
**Merge conflicts detected** → do not build a plan. Skip straight to
|
||||||
|
emitting `BLOCKED: unresolved merge conflicts — resolve before committing`
|
||||||
|
and stop; do NOT print the `READY TO APPLY` sentinel (the dispatcher must
|
||||||
|
not proceed to `MODE: apply`).
|
||||||
|
|
||||||
### Phase 2: Reconstruct the development steps
|
### Phase 2: Reconstruct the development steps
|
||||||
|
|
||||||
Read the actual diffs and file contents. Reconstruct **what happened in
|
Read the actual diffs and file contents. Reconstruct **what happened in
|
||||||
@@ -73,42 +98,19 @@ Guidelines:
|
|||||||
- **Order matters.** Commits should read in the order work happened.
|
- **Order matters.** Commits should read in the order work happened.
|
||||||
Earlier steps first.
|
Earlier steps first.
|
||||||
|
|
||||||
### Phase 2.5: Checkpoint — present plan, get approval
|
**Sensitive files** (.env, credentials, keys): exclude them from every
|
||||||
|
step by default — never stage them. Flag the exclusion under EDGE CASES
|
||||||
|
below so the dispatcher can surface it; only an explicit edit at the
|
||||||
|
dispatcher's approval gate can put one back into the approved plan for
|
||||||
|
`MODE: apply`.
|
||||||
|
|
||||||
Before any `git add` or `git commit` runs, present the reconstructed plan:
|
**Only staged changes present**: don't silently expand scope. Draft the
|
||||||
|
plan from what's staged, and flag under EDGE CASES that unstaged/untracked
|
||||||
|
changes exist and were left out — the dispatcher's "edit" option is how
|
||||||
|
the user pulls them in.
|
||||||
|
|
||||||
```
|
**Single logical change**: one commit is the right answer — don't
|
||||||
COMMIT PLAN — <N> step(s) from working tree
|
artificially split what was done as one action.
|
||||||
|
|
||||||
1. <type>(<scope>): <short description>
|
|
||||||
files: <a.ts, b.css, c.md>
|
|
||||||
2. <type>(<scope>): <short description>
|
|
||||||
files: <d.py>
|
|
||||||
...
|
|
||||||
|
|
||||||
Approve? (all / <numbers> / edit <n> / skip)
|
|
||||||
```
|
|
||||||
|
|
||||||
- `all` → execute the full plan in Phase 3.
|
|
||||||
- `<numbers>` (e.g. `1,3`) → execute only the selected steps.
|
|
||||||
- `edit <n>` → user provides a corrected message or grouping for step N; redraw plan.
|
|
||||||
- `skip` → exit cleanly, no commits created.
|
|
||||||
|
|
||||||
This gate is mandatory. Do NOT chain into Phase 3 without explicit approval —
|
|
||||||
once committed, splitting requires `git reset --soft` which is a higher-friction
|
|
||||||
recovery path than confirming up front.
|
|
||||||
|
|
||||||
### Phase 3: Execute commits
|
|
||||||
|
|
||||||
After approval in Phase 2.5, for each approved step in chronological order:
|
|
||||||
|
|
||||||
1. Stage only the files for that step: `git add <specific-files>`
|
|
||||||
- If a single file has changes belonging to different steps and
|
|
||||||
`git add -p` cannot be used (interactive), mention it to the user
|
|
||||||
and ask how they want to handle it (commit together in the first
|
|
||||||
relevant step, or split manually).
|
|
||||||
2. Create the commit with a message that describes the step
|
|
||||||
3. Verify with `git status` that the right files were committed
|
|
||||||
|
|
||||||
### Commit message format
|
### Commit message format
|
||||||
|
|
||||||
@@ -125,47 +127,111 @@ Types: `feat`, `fix`, `refactor`, `chore`, `docs`, `test`, `style`, `perf`
|
|||||||
Keep the first line under 72 characters. The body explains motivation
|
Keep the first line under 72 characters. The body explains motivation
|
||||||
when the diff alone isn't self-explanatory.
|
when the diff alone isn't self-explanatory.
|
||||||
|
|
||||||
### Edge cases
|
### Capitalize candidates (draft only — decided later, written in `MODE: apply`)
|
||||||
|
|
||||||
- **No changes**: tell the user there's nothing to commit
|
Inspect the reconstructed steps as a whole and draft candidates, same
|
||||||
- **Only staged changes**: respect what's already staged — ask if the
|
criteria as the standalone `/capitalize` flow:
|
||||||
user wants to commit just those, or also include unstaged/untracked
|
|
||||||
- **Merge conflicts**: don't try to commit — tell the user to resolve
|
|
||||||
- **Single logical change**: one commit is the right answer — don't
|
|
||||||
artificially split what was done as one action
|
|
||||||
- **Sensitive files** (.env, credentials, keys): warn the user and
|
|
||||||
exclude them from commits by default
|
|
||||||
|
|
||||||
### Phase 4: Capitalize (memory registries)
|
- Any step that represents a **design/architecture choice** (new dependency,
|
||||||
|
refactor with rationale, API shape decision) → draft an entry for
|
||||||
After all commits are created, inspect the set as a whole:
|
|
||||||
|
|
||||||
- Any commit that represents a **design/architecture choice** (new dependency,
|
|
||||||
refactor with rationale, API shape decision) → propose an entry in
|
|
||||||
`.claude/memory/decisions.md` (BDR-XXX) with pre-filled alternatives.
|
`.claude/memory/decisions.md` (BDR-XXX) with pre-filled alternatives.
|
||||||
- Any commit that resolves a **non-trivial bug with a root cause** → propose
|
- Any step that resolves a **non-trivial bug with a root cause** → draft an
|
||||||
an entry in `.claude/memory/blockers.md` (BLK-XXX, status: resolved).
|
entry for `.claude/memory/blockers.md` (BLK-XXX, status: resolved).
|
||||||
- Any commit whose content taught something **reusable beyond the immediate fix**
|
- Any step whose content taught something **reusable beyond the immediate
|
||||||
(a pattern, a gotcha, a surprising API behaviour) → propose an entry in
|
fix** (a pattern, a gotcha, a surprising API behaviour) → draft an entry
|
||||||
`.claude/memory/learnings.md` (LRN-XXX).
|
for `.claude/memory/learnings.md` (LRN-XXX).
|
||||||
|
|
||||||
|
**Language rule**: draft entries in English (see CLAUDE.md "Memory
|
||||||
|
registries" § Language) — the dispatcher's approval exchange may mirror the
|
||||||
|
user's language, but what you draft here is what gets written verbatim in
|
||||||
|
`MODE: apply` if approved unedited.
|
||||||
|
|
||||||
|
If every step is pure chore/docs/style with nothing to log, draft nothing.
|
||||||
|
|
||||||
|
### Emit the COMMIT PLAN and stop
|
||||||
|
|
||||||
|
This is the end of `MODE: propose`. Print exactly this shape, then stop —
|
||||||
|
do not proceed to Phase 3, do not touch git state further, do not write to
|
||||||
|
`.claude/memory`:
|
||||||
|
|
||||||
Present grouped candidates:
|
|
||||||
```
|
```
|
||||||
CAPITALIZE — depuis les <N> commits créés
|
COMMIT PLAN — <N> step(s) from working tree
|
||||||
[decisions.md] BDR-XXX — <titre> (ref commit <hash>)
|
|
||||||
[blockers.md] BLK-XXX — <friction> — resolved (ref commit <hash>)
|
1. <type>(<scope>): <short description>
|
||||||
|
files: <a.ts, b.css, c.md>
|
||||||
|
2. <type>(<scope>): <short description>
|
||||||
|
files: <d.py>
|
||||||
|
...
|
||||||
|
|
||||||
|
EDGE CASES:
|
||||||
|
- <e.g. "sensitive file .env excluded from step 2">
|
||||||
|
- <e.g. "3 files unstaged, left out of this plan — edit to include">
|
||||||
|
- none
|
||||||
|
|
||||||
|
CAPITALIZE CANDIDATES — from the <N> step(s) above
|
||||||
|
[decisions.md] BDR-XXX — <titre> (ref step <n>)
|
||||||
|
[blockers.md] BLK-XXX — <friction> — resolved (ref step <n>)
|
||||||
[learnings.md] LRN-XXX — <pattern>
|
[learnings.md] LRN-XXX — <pattern>
|
||||||
Valider ? (all / <IDs> / edit / skip)
|
... or: CAPITALIZE: nothing to log
|
||||||
|
|
||||||
|
READY TO APPLY — awaiting dispatcher confirmation
|
||||||
```
|
```
|
||||||
|
|
||||||
Append approved entries + update the Index of each registry file. Add a line to today's heading in `.claude/memory/journal.md` summarising the commit batch.
|
---
|
||||||
|
|
||||||
**Language rule**: written entries are ALWAYS in English (see CLAUDE.md "Memory registries" § Language). The interactive gate may mirror the user's language; the appended entries must not.
|
## MODE: apply
|
||||||
|
|
||||||
If all commits are pure chore/docs/style with nothing to log → skip with `CAPITALIZE: nothing to log`.
|
### Input (in the dispatch prompt)
|
||||||
|
|
||||||
**Then commit the memory** — follow `$HOME/.claude/lib/capitalize-commit.md`: it
|
- The APPROVED COMMIT PLAN: final step list — numbers, messages, and
|
||||||
surgically commits what capitalize just wrote (`.claude/memory` + `.claude/tasks`
|
files, exactly as confirmed by the user (may be a subset of, or edited
|
||||||
only, never `git add -A`) as one `chore(memory)` commit, reports the memory-commit
|
from, the `MODE: propose` output).
|
||||||
hash, and no-ops if nothing was written. This is a separate commit from the Phase 3
|
- The APPROVED CAPITALIZE ENTRIES: verbatim registry text to write, or
|
||||||
code commits — their hashes are already anchored inside the entries.
|
`none`/`skip`.
|
||||||
|
|
||||||
|
Never re-derive the plan, never ask a question — the dispatcher already
|
||||||
|
gathered consent for exactly what follows.
|
||||||
|
|
||||||
|
### Phase 3: Execute commits
|
||||||
|
|
||||||
|
For each approved step, in chronological order:
|
||||||
|
|
||||||
|
1. Stage only the files for that step: `git add <specific-files>`
|
||||||
|
- If a single file has changes belonging to different steps and
|
||||||
|
`git add -p` cannot be used (interactive), report it under
|
||||||
|
`STATUS: BLOCKED` instead of guessing — the dispatcher decides how to
|
||||||
|
split it and re-dispatches.
|
||||||
|
2. Create the commit with the approved message.
|
||||||
|
3. Verify with `git status` that the right files were committed.
|
||||||
|
|
||||||
|
### Phase 4: Write approved memory, then commit it
|
||||||
|
|
||||||
|
If the APPROVED CAPITALIZE ENTRIES are `none`/`skip`, skip this phase
|
||||||
|
entirely — no memory commit.
|
||||||
|
|
||||||
|
Otherwise:
|
||||||
|
1. Append the approved entries verbatim to their target registry file(s)
|
||||||
|
(`.claude/memory/decisions.md`, `blockers.md`, `learnings.md`) and
|
||||||
|
update each file's `## Index` table. Add a one-line summary of the
|
||||||
|
commit batch to today's heading in `.claude/memory/journal.md`.
|
||||||
|
2. **Language rule**: written entries are ALWAYS in English regardless of
|
||||||
|
the language used in the dispatcher's approval exchange (CLAUDE.md
|
||||||
|
"Memory registries" § Language).
|
||||||
|
3. **Then commit the memory** — follow
|
||||||
|
`$HOME/.claude/lib/capitalize-commit.md`: it surgically commits what
|
||||||
|
was just written (`.claude/memory` + `.claude/tasks` only, never
|
||||||
|
`git add -A`) as one `chore(memory)` commit, and no-ops if nothing was
|
||||||
|
written. This is a separate commit from the Phase 3 code commits — their
|
||||||
|
hashes are already anchored inside the entries.
|
||||||
|
|
||||||
|
### Report
|
||||||
|
|
||||||
|
End with exactly this report (your final message):
|
||||||
|
|
||||||
|
```
|
||||||
|
COMMIT-EXEC REPORT
|
||||||
|
STATUS : DONE | BLOCKED
|
||||||
|
COMMITS : <hash> <subject> (one line per Phase-3 commit, chronological)
|
||||||
|
MEMORY : <memory-commit hash> | none
|
||||||
|
NOTES : <DONE: none | BLOCKED: the blocker verbatim>
|
||||||
|
```
|
||||||
|
|||||||
@@ -16,15 +16,98 @@ allowed-tools:
|
|||||||
- AskUserQuestion
|
- AskUserQuestion
|
||||||
---
|
---
|
||||||
|
|
||||||
Load and follow strictly: `$HOME/.claude/agents/commit-changer.md`.
|
# /commit-change — propose → confirm → apply dispatcher
|
||||||
|
|
||||||
If unreachable, emit `Commit-changer agent missing.` and STOP. Never auto-commit blind — a wrong group is harder to undo than not committing.
|
Grouping and committing both run on the sonnet-pinned `commit-changer`
|
||||||
|
subagent (dispatch makes the pin effective). No inline reflection happens
|
||||||
|
in this dispatcher to protect, so there is no model gate. This dispatcher
|
||||||
|
owns the two approval gates that used to live inside the subagent:
|
||||||
|
commit-plan approval and capitalize approval — the subagent never asks;
|
||||||
|
`MODE: propose` only proposes, `MODE: apply` only executes what this
|
||||||
|
dispatcher confirms. Never auto-commit blind — a wrong group is harder to
|
||||||
|
undo than not committing.
|
||||||
|
|
||||||
Pre-flight checks (the agent should also perform, but flag here):
|
## STEP 0 — Pre-flight (STOP conditions, before any dispatch)
|
||||||
- Detached HEAD or unmerged conflicts → STOP, report state.
|
|
||||||
- Identity unconfigured (`git config user.email` empty) → STOP, ask user.
|
|
||||||
- On a protected base (`main`/`develop`) the agent runs the gitflow
|
|
||||||
aiguillage (Phase 0) and branches to `chore/*` before committing — code
|
|
||||||
never lands directly on a protected branch.
|
|
||||||
|
|
||||||
$ARGUMENTS
|
```bash
|
||||||
|
git rev-parse --abbrev-ref HEAD # "HEAD" = detached
|
||||||
|
git status --porcelain=v1 | grep -c '^UU\|^AA\|^DD' # unmerged conflicts
|
||||||
|
git status --porcelain=v1 | wc -l # nothing pending?
|
||||||
|
git config user.email
|
||||||
|
```
|
||||||
|
|
||||||
|
- Detached HEAD → STOP, report the state, do not dispatch.
|
||||||
|
- Any unmerged conflict entries (`UU`/`AA`/`DD`) → STOP, tell the user to
|
||||||
|
resolve conflicts first, do not dispatch.
|
||||||
|
- Nothing pending (`git status --porcelain` empty) → STOP, tell the user
|
||||||
|
there's nothing to commit.
|
||||||
|
- `git config user.email` empty → STOP, ask the user to configure identity
|
||||||
|
first, do not dispatch.
|
||||||
|
|
||||||
|
On a protected base (`main`/`develop`) the subagent runs the gitflow
|
||||||
|
aiguillage itself inside `MODE: propose` (its Phase 0) and branches to
|
||||||
|
`chore/*` before drafting the plan — code never lands directly on a
|
||||||
|
protected branch.
|
||||||
|
|
||||||
|
## STEP 1 — Propose
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent(subagent_type="commit-changer")
|
||||||
|
prompt: "MODE: propose
|
||||||
|
$ARGUMENTS"
|
||||||
|
```
|
||||||
|
|
||||||
|
Read the returned `COMMIT PLAN` + `EDGE CASES` + `CAPITALIZE CANDIDATES`,
|
||||||
|
terminated by `READY TO APPLY — awaiting dispatcher confirmation`.
|
||||||
|
|
||||||
|
The subagent reported `BLOCKED: unresolved merge conflicts...` instead of a
|
||||||
|
plan (a race with STEP 0) → STOP, surface it, do not proceed.
|
||||||
|
|
||||||
|
## STEP 2 — Gate 1: commit-plan approval
|
||||||
|
|
||||||
|
Show the `COMMIT PLAN` and any `EDGE CASES` verbatim, then:
|
||||||
|
|
||||||
|
```
|
||||||
|
AskUserQuestion:
|
||||||
|
Approve the commit plan? (all / <numbers> / edit <n> / skip)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `all` → every step in the plan is approved as-is.
|
||||||
|
- `<numbers>` (e.g. `1,3`) → only those steps are approved; the rest stay
|
||||||
|
uncommitted for a later run.
|
||||||
|
- `edit <n>` → collect the corrected message/grouping for step N from the
|
||||||
|
user, redraw the plan (this dispatcher owns the text, no re-dispatch
|
||||||
|
needed), show it again and re-ask.
|
||||||
|
- `skip` → exit cleanly, no commits created, no `MODE: apply` dispatch.
|
||||||
|
|
||||||
|
## STEP 3 — Gate 2: capitalize approval
|
||||||
|
|
||||||
|
If the STEP 1 output said `CAPITALIZE: nothing to log`, skip this gate —
|
||||||
|
treat the capitalize entries as `none` and go straight to STEP 4.
|
||||||
|
|
||||||
|
Otherwise show the `CAPITALIZE CANDIDATES` block, then:
|
||||||
|
|
||||||
|
```
|
||||||
|
AskUserQuestion:
|
||||||
|
Valider les entrées mémoire ? (all / <IDs> / skip)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `all` → every candidate entry is approved verbatim.
|
||||||
|
- `<IDs>` (e.g. `BDR-041,LRN-019`) → only those entries are approved.
|
||||||
|
- `skip` → no memory write; `MODE: apply` still runs for the code commits.
|
||||||
|
|
||||||
|
## STEP 4 — Apply
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent(subagent_type="commit-changer")
|
||||||
|
prompt: "MODE: apply
|
||||||
|
APPROVED PLAN: <the STEP-2-approved steps — numbers, messages, files,
|
||||||
|
exactly as confirmed, including any edits>
|
||||||
|
APPROVED CAPITALIZE ENTRIES: <the STEP-3-approved entries verbatim, or none>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Parse the `COMMIT-EXEC REPORT`:
|
||||||
|
- `STATUS: DONE` → report the `COMMITS` + `MEMORY` hashes to the user.
|
||||||
|
- `STATUS: BLOCKED` → surface the blocker verbatim and stop. Do not retry
|
||||||
|
automatically — a blocked step (e.g. one file needs an interactive
|
||||||
|
`git add -p` split) needs a human decision.
|
||||||
|
|||||||
Reference in New Issue
Block a user