diff --git a/agents/commit-changer.md b/agents/commit-changer.md index e7b4e95..01981a3 100644 --- a/agents/commit-changer.md +++ b/agents/commit-changer.md @@ -1,7 +1,8 @@ --- 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. -tools: Bash, Read, Grep, Glob, AskUserQuestion +tools: Bash, Read, Grep, Glob +model: sonnet --- # 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 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) @@ -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/` derived 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`, -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 `$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 -proceeding. +current branch state as an edge case in the emitted plan instead of +branching, so the dispatcher can ask the user which branch to commit on. ### 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 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 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. 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. -``` -COMMIT PLAN — step(s) from working tree - - 1. (): - files: - 2. (): - files: - ... - -Approve? (all / / edit / skip) -``` - -- `all` → execute the full plan in Phase 3. -- `` (e.g. `1,3`) → execute only the selected steps. -- `edit ` → 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 ` - - 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 +**Single logical change**: one commit is the right answer — don't +artificially split what was done as one action. ### 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 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 -- **Only staged changes**: respect what's already staged — ask if the - 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 +Inspect the reconstructed steps as a whole and draft candidates, same +criteria as the standalone `/capitalize` flow: -### Phase 4: Capitalize (memory registries) - -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 +- Any step that represents a **design/architecture choice** (new dependency, + refactor with rationale, API shape decision) → draft an entry for `.claude/memory/decisions.md` (BDR-XXX) with pre-filled alternatives. -- Any commit that resolves a **non-trivial bug with a root cause** → propose - an entry in `.claude/memory/blockers.md` (BLK-XXX, status: resolved). -- Any commit whose content taught something **reusable beyond the immediate fix** - (a pattern, a gotcha, a surprising API behaviour) → propose an entry in - `.claude/memory/learnings.md` (LRN-XXX). +- Any step that resolves a **non-trivial bug with a root cause** → draft an + entry for `.claude/memory/blockers.md` (BLK-XXX, status: resolved). +- Any step whose content taught something **reusable beyond the immediate + fix** (a pattern, a gotcha, a surprising API behaviour) → draft an entry + 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 commits créés - [decisions.md] BDR-XXX — (ref commit ) - [blockers.md] BLK-XXX — — resolved (ref commit ) +COMMIT PLAN — step(s) from working tree + + 1. (): + files: + 2. (): + files: + ... + +EDGE CASES: + - + - + - none + +CAPITALIZE CANDIDATES — from the step(s) above + [decisions.md] BDR-XXX — (ref step ) + [blockers.md] BLK-XXX — — resolved (ref step ) [learnings.md] LRN-XXX — -Valider ? (all / / 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 -surgically commits what capitalize just wrote (`.claude/memory` + `.claude/tasks` -only, never `git add -A`) as one `chore(memory)` commit, reports the memory-commit -hash, 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. +- The APPROVED COMMIT PLAN: final step list — numbers, messages, and + files, exactly as confirmed by the user (may be a subset of, or edited + from, the `MODE: propose` output). +- The APPROVED CAPITALIZE ENTRIES: verbatim registry text to write, or + `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 ` + - 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 : (one line per Phase-3 commit, chronological) +MEMORY : | none +NOTES : +``` diff --git a/skills/commit-change/SKILL.md b/skills/commit-change/SKILL.md index 9f4ce86..38b7dfb 100644 --- a/skills/commit-change/SKILL.md +++ b/skills/commit-change/SKILL.md @@ -16,15 +16,98 @@ allowed-tools: - 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): -- 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. +## STEP 0 — Pre-flight (STOP conditions, before any dispatch) -$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 / / edit / skip) +``` + +- `all` → every step in the plan is approved as-is. +- `` (e.g. `1,3`) → only those steps are approved; the rest stay + uncommitted for a later run. +- `edit ` → 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 / / skip) +``` + +- `all` → every candidate entry is approved verbatim. +- `` (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: +APPROVED CAPITALIZE ENTRIES: " +``` + +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.