[ADD] script todo : déployer /git_prepare_merge depuis le menu Claude
Une fusion apporte plusieurs commits d'un coup, et rien ne guidait les deux écrits qu'elle demande : l'entrée de changelog et le message de merge. La commande déployée impose la source — CHANGELOG.base.md, les fichiers générés étant perdus au prochain doc_markdown — et le corps bilingue avec son trailer, que le garde-fou ne verra jamais, commit-msg ignorant tout message ouvrant sur « Merge ». Elle prépare, elle ne fusionne pas : le message part dans tasks/, non versionné. Vérifié : 45 tests du menu, et un déploiement dans un HOME jetable que l'écran de contexte relit « à jour » face à son gabarit. --- EN --- A merge lands several commits at once, and nothing guided the two pieces of writing it needs: the changelog entry and the merge message. The deployed command imposes the source — CHANGELOG.base.md, the generated files being lost at the next doc_markdown — and the bilingual body with its trailer, which the guard rail never sees, commit-msg skipping any message opening on « Merge ». It prepares, it does not merge: the message goes to tasks/, which is not versioned. Checked: 45 menu tests, and a deployment into a throwaway HOME that the context screen reads back as up to date against its template. Assisted-by: Claude Opus 5
This commit is contained in:
parent
8bdbf42a31
commit
c32e7d87c9
3 changed files with 188 additions and 1 deletions
170
conf/template_claude_commands_git_prepare_merge.md
Normal file
170
conf/template_claude_commands_git_prepare_merge.md
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
---
|
||||||
|
name: git_prepare_merge
|
||||||
|
description: "ERPLibre merge preparation: changelog entry, then the merge message for the current branch."
|
||||||
|
disable-model-invocation: true
|
||||||
|
allowed-tools:
|
||||||
|
- Bash(git status:*)
|
||||||
|
- Bash(git branch:*)
|
||||||
|
- Bash(git log:*)
|
||||||
|
- Bash(git diff:*)
|
||||||
|
- Bash(git merge-base:*)
|
||||||
|
- Bash(sed:*)
|
||||||
|
- Bash(make doc_markdown:*)
|
||||||
|
- Bash(python3:*)
|
||||||
|
- Read
|
||||||
|
- Edit
|
||||||
|
- Write
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- Current branch: !`git branch --show-current`
|
||||||
|
- Branch commits: !`git log --oneline $(git merge-base HEAD master)..HEAD`
|
||||||
|
- Files touched: !`git diff --stat $(git merge-base HEAD master)..HEAD`
|
||||||
|
- Working tree: !`git status --porcelain`
|
||||||
|
- Changelog head: !`sed -n '24,45p' CHANGELOG.base.md`
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
Prepare the merge of the CURRENT branch into its integration branch. Two
|
||||||
|
deliverables, in this order: the changelog entry, then the merge message.
|
||||||
|
Nothing is merged here — `/git_prepare_merge` prepares, the human merges.
|
||||||
|
|
||||||
|
### 0. Read the branch
|
||||||
|
|
||||||
|
`master` is production, `develop` is where the work lands. Take the target
|
||||||
|
from where the branch forked: `git merge-base HEAD develop` and
|
||||||
|
`git merge-base HEAD master`, the closer of the two names the target.
|
||||||
|
|
||||||
|
Read the WHOLE branch before writing a word — `git log -p <base>..HEAD` for
|
||||||
|
the commits, `git diff <base>..HEAD` for the net result. A merge message
|
||||||
|
summarises what the branch delivers, which is rarely the concatenation of its
|
||||||
|
subjects: commits that undo each other cancel, and a fix to a feature added on
|
||||||
|
the same branch is part of the feature, not a separate line.
|
||||||
|
|
||||||
|
Stop and say so, rather than inventing, when the branch is empty, when it is
|
||||||
|
already merged, or when the working tree carries changes not yet committed —
|
||||||
|
uncommitted work is not part of the merge and must not be described as if it
|
||||||
|
were.
|
||||||
|
|
||||||
|
### 1. The changelog entry
|
||||||
|
|
||||||
|
`CHANGELOG.base.md` at the repository root is the SOURCE. `CHANGELOG.md` and
|
||||||
|
`CHANGELOG.fr.md` are generated by mmg and every direct edit to them is lost
|
||||||
|
at the next `make doc_markdown` — never open them to write.
|
||||||
|
|
||||||
|
The entry goes under `## [Unreleased]`, in the section that fits: Added /
|
||||||
|
Ajouté, Changed / Modifié, Fixed / Corrigé, Removed / Retiré, Security /
|
||||||
|
Sécurité. Create the pair of headings if the section does not exist yet, in
|
||||||
|
the file's own order.
|
||||||
|
|
||||||
|
The file alternates language blocks with markers. Within one section the
|
||||||
|
English bullets sit under `<!-- [en] -->` and the French translation under
|
||||||
|
`<!-- [fr] -->`, in the SAME order: the two lists are read side by side, and a
|
||||||
|
bullet added to one language only leaves the other half wrong. Nothing goes
|
||||||
|
under `<!-- [common] -->` but the version headings.
|
||||||
|
|
||||||
|
What a bullet says: what the software now DOES or REFUSES, in the present, for
|
||||||
|
someone who was not on this branch. It is longer than a commit subject and
|
||||||
|
shorter than the commit body — the reader is choosing whether to upgrade, not
|
||||||
|
reviewing the diff. Keep the failure mode removed, the figure that bounds it,
|
||||||
|
the flag or the file a user has to know. Drop the internals nobody outside
|
||||||
|
calls.
|
||||||
|
|
||||||
|
The two rules of `.claude/rules/04-code-conventions.md` hold here as
|
||||||
|
everywhere: nothing identifying — no customer, no real database, no host, no
|
||||||
|
address, no account path — and the code as the subject, never the session
|
||||||
|
that produced it.
|
||||||
|
|
||||||
|
Regenerate afterwards, and stage the three files together, the generated pair
|
||||||
|
being what most readers actually open:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make doc_markdown
|
||||||
|
git status --porcelain CHANGELOG.base.md CHANGELOG.md CHANGELOG.fr.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. The merge message
|
||||||
|
|
||||||
|
Resolve `{MODEL}` exactly as `/commit` does — the trailer is required here
|
||||||
|
too, a merge message being as AI-assisted as any other. Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -c "
|
||||||
|
import glob, json, os, sys
|
||||||
|
sid = os.environ.get('CLAUDE_CODE_SESSION_ID', '')
|
||||||
|
hits = glob.glob(os.path.expanduser('~/.claude/projects/*/%s.jsonl' % sid)) if sid else []
|
||||||
|
mid = ''
|
||||||
|
for path in hits[:1]:
|
||||||
|
with open(path) as fh:
|
||||||
|
for line in fh:
|
||||||
|
try:
|
||||||
|
m = json.loads(line).get('message', {}).get('model', '')
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if m and not m.startswith('<'):
|
||||||
|
mid = m
|
||||||
|
if not mid:
|
||||||
|
sys.exit('UNKNOWN')
|
||||||
|
mid = mid.removeprefix('claude-')
|
||||||
|
parts = [p for p in mid.split('-') if not (len(p) == 8 and p.isdigit())]
|
||||||
|
print('Claude %s %s' % (parts[0].capitalize(), '.'.join(parts[1:])))
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
The shape, as this repository writes it:
|
||||||
|
|
||||||
|
```
|
||||||
|
Merge branch '<branch>'
|
||||||
|
|
||||||
|
[TAG] scope: what the branch delivers, imperative, 72 characters maximum
|
||||||
|
|
||||||
|
<N> commits. Why the branch existed: the failure mode it removes, the
|
||||||
|
figure that bounds it, what was verified and how. Wrap at 80 characters.
|
||||||
|
|
||||||
|
--- EN ---
|
||||||
|
|
||||||
|
The same body, translated.
|
||||||
|
|
||||||
|
Assisted-by: {MODEL}
|
||||||
|
```
|
||||||
|
|
||||||
|
The first line stays `Merge branch '<branch>'` — git writes it, tools read it,
|
||||||
|
and a merge whose first line says something else no longer looks like a merge
|
||||||
|
in `git log --oneline`. The tagged line beneath it is what a reader gets from
|
||||||
|
`--oneline` on the second row and from a release note, so it carries the same
|
||||||
|
duty as a commit subject: name the part of the system, then what is now
|
||||||
|
different about it. The evidence — the symptom, the quoted error, the
|
||||||
|
metaphor — belongs in the body.
|
||||||
|
|
||||||
|
The body: the same budget as a commit, per language, and here a merge covers
|
||||||
|
several commits, so it is a SUMMARY and not a list. Open by stating how many
|
||||||
|
commits the branch carries, then say what they add up to. No bullet list, no
|
||||||
|
per-commit rundown: `git log <base>..HEAD` already gives that, and a body
|
||||||
|
repeating it teaches nothing.
|
||||||
|
|
||||||
|
The bilingual rule holds — body, then the marker naming the language of what
|
||||||
|
FOLLOWS, then the translation — as does the ban on naming an AI in
|
||||||
|
`Co-authored-by:`.
|
||||||
|
|
||||||
|
**The hook does not check this one.** `script/git/hooks/commit-msg` skips any
|
||||||
|
message beginning with `Merge `, along with `Revert `, `fixup!` and `squash!`.
|
||||||
|
Length, addresses and account paths pass unchallenged here, so the discipline
|
||||||
|
is entirely yours.
|
||||||
|
|
||||||
|
### 3. Hand it over
|
||||||
|
|
||||||
|
Write the message to `tasks/merge_message.txt` — `tasks/` is not versioned,
|
||||||
|
which is why the repository sends working material there — and print the two
|
||||||
|
commands the human runs, with `--no-ff` so the branch keeps a merge commit and
|
||||||
|
its history stays readable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git switch <target>
|
||||||
|
git merge --no-ff <branch> -F tasks/merge_message.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not run them. Do not switch branch, do not merge, do not push: the merge is
|
||||||
|
the human's decision and the last chance to read the message before it is
|
||||||
|
permanent. Report, in a sentence each, the changelog section written to and
|
||||||
|
the number of commits summarised.
|
||||||
|
|
@ -3050,6 +3050,11 @@ class TODO(
|
||||||
print(f"🤖 {t('Deploy Claude Code commands!')}")
|
print(f"🤖 {t('Deploy Claude Code commands!')}")
|
||||||
choices = [
|
choices = [
|
||||||
{"prompt_description": t("Commit - OCA/Odoo commit command")},
|
{"prompt_description": t("Commit - OCA/Odoo commit command")},
|
||||||
|
{
|
||||||
|
"prompt_description": t(
|
||||||
|
"Git prepare merge - Git merge preparation command"
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"prompt_description": t(
|
"prompt_description": t(
|
||||||
"Todo Add Command - Add a command to todo.py menu"
|
"Todo Add Command - Add a command to todo.py menu"
|
||||||
|
|
@ -3071,11 +3076,16 @@ class TODO(
|
||||||
personalize=True,
|
personalize=True,
|
||||||
)
|
)
|
||||||
elif status == "2":
|
elif status == "2":
|
||||||
|
self._setup_claude_command(
|
||||||
|
"git_prepare_merge",
|
||||||
|
"template_claude_commands_git_prepare_merge.md",
|
||||||
|
)
|
||||||
|
elif status == "3":
|
||||||
self._setup_claude_command(
|
self._setup_claude_command(
|
||||||
"todo_add_command",
|
"todo_add_command",
|
||||||
"template_claude_commands_todo_add_command.md",
|
"template_claude_commands_todo_add_command.md",
|
||||||
)
|
)
|
||||||
elif status == "3":
|
elif status == "4":
|
||||||
self._list_claude_commands()
|
self._list_claude_commands()
|
||||||
else:
|
else:
|
||||||
print(t("Command not found !"))
|
print(t("Command not found !"))
|
||||||
|
|
@ -3203,6 +3213,9 @@ class TODO(
|
||||||
print(f"{t('Deployed commands'):<22} ~/.claude/commands/")
|
print(f"{t('Deployed commands'):<22} ~/.claude/commands/")
|
||||||
gabarits = {
|
gabarits = {
|
||||||
"commit": "template_claude_commands_commit.md",
|
"commit": "template_claude_commands_commit.md",
|
||||||
|
"git_prepare_merge": (
|
||||||
|
"template_claude_commands_git_prepare_merge.md"
|
||||||
|
),
|
||||||
"todo_add_command": "template_claude_commands_todo_add_command.md",
|
"todo_add_command": "template_claude_commands_todo_add_command.md",
|
||||||
}
|
}
|
||||||
for nom, gabarit in sorted(gabarits.items()):
|
for nom, gabarit in sorted(gabarits.items()):
|
||||||
|
|
|
||||||
|
|
@ -1176,6 +1176,10 @@ TRANSLATIONS = {
|
||||||
"fr": "Commit - Commande de commit OCA/Odoo",
|
"fr": "Commit - Commande de commit OCA/Odoo",
|
||||||
"en": "Commit - OCA/Odoo commit command",
|
"en": "Commit - OCA/Odoo commit command",
|
||||||
},
|
},
|
||||||
|
"Git prepare merge - Git merge preparation command": {
|
||||||
|
"fr": "Git prepare merge - Commande de préparation merge git",
|
||||||
|
"en": "Git prepare merge - Git merge preparation command",
|
||||||
|
},
|
||||||
"Todo Add Command - Add a command to todo.py menu": {
|
"Todo Add Command - Add a command to todo.py menu": {
|
||||||
"fr": "Todo Add Command - Ajouter une commande au menu todo.py",
|
"fr": "Todo Add Command - Ajouter une commande au menu todo.py",
|
||||||
"en": "Todo Add Command - Add a command to todo.py menu",
|
"en": "Todo Add Command - Add a command to todo.py menu",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue