erplibre/conf/template_claude_commands_commit.md
Mathieu Benoit 0e5441ac39 [ADD] commentaires : un relevé non bloquant, sa règle, le code nettoyé
Rien ne relevait les commentaires hors convention. L'outil lit commentaires et
docstrings, jamais le code autour, et rend deux familles inégales :
l'identifiant — adresse, courriel, chemin de compte, nom de la liste privée —
est une trouvaille ; le récit — témoignage, date, personne — un signal à relire.

Le hook pre-commit le lance sur l'index, sort toujours en 0 — un contrôle
bloquant à cette échelle se fait désinstaller — et parle quand l'outil échoue.
La règle du générateur et sa doc portent le nettoyage au fur et à mesure, et
l'exemple d'un interdit s'invente : base, adresse, compte et hôte en prennent un.

L'écran du contexte est posé, sans entrée de menu. Vérifié : 252 tests des six
fichiers d'essai touchés passent.

--- EN ---

Nothing reported the comments that break the convention. The tool reads
comments and docstrings, never the code around them, and returns two unequal
families: identifying data — address, e-mail, account path, private-list name
— is a finding; narrative — witness marker, date, person — a signal to re-read.

The pre-commit hook runs it on the index, always exits 0 — a blocking check at
that scale gets uninstalled — and speaks when the tool fails. The generator
rule and its doc carry the clean-up as you go, and a forbidden thing's example
is invented: a real database, address, account and host each take one.

The context screen is in place, with no menu entry. Checked: 252 tests of the
six touched fixture files pass.

Assisted-by: Claude Opus 5
2026-08-30 06:08:30 -04:00

260 lines
10 KiB
Markdown

---
name: commit
description: "ERPLibre commit: OCA tag, bilingual body, Assisted-by trailer per AI_POLICY.md."
disable-model-invocation: true
allowed-tools:
- Bash(git add:*)
- Bash(git status:*)
- Bash(git commit:*)
- Bash(git diff:*)
- Bash(git log:*)
- Bash(python3:*)
---
## Context
- Git status: !`git status`
- Full diff: !`git diff HEAD`
- Current branch: !`git branch --show-current`
- Last 5 commits (for style reference): !`git log --oneline -5`
## Task
Write a commit that satisfies `AI_POLICY.md` — the OCA generative AI policy
ERPLibre adopts — and the conventions below.
### Resolve the model — `{MODEL}`
Run this first. It reads the model from the CURRENT session transcript, which
is the only source that stays right when the model is switched mid-session
with `/model` or a CLI flag:
```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:])))
"
```
It prints the trailer value: `Claude Opus 5`, `Claude Sonnet 4.6`,
`Claude Haiku 4.5`. There is deliberately no fallback name — on `UNKNOWN`,
use the model you know you are running as, and never a value read from a
settings file: `~/.claude/settings.json` usually has no `model` key at all,
so it would quietly declare the default instead of the truth.
### Tags
| Tag | Usage |
|-----|-------|
| `[UPD]` | Update existing code, data or configuration |
| `[FIX]` | Bug fix |
| `[ADD]` | New module, file or capability |
| `[IMP]` | Improvement to something that already works |
| `[REF]` | Refactoring, no observable behaviour change |
| `[REM]` | Remove code or module |
| `[MOV]` | Move or rename |
| `[I18N]` | Translations |
The first five cover every one of the last 400 commits. Reach for `[REM]`,
`[MOV]` or `[I18N]` only when one of them genuinely fits better.
### Format
```
[TAG] scope: short description in imperative mood
Explain WHY the change was made — the diff already shows what. Name the
failure mode, and what was measured rather than assumed, in words a
stranger to this site can read: the software, the version, the class of
situation — never a customer, a real database, a machine or an address.
Wrap at 80 characters.
--- FR ---
The same body, translated.
Assisted-by: {MODEL}
```
### The subject line
The subject is read a hundred times for every time the body is read: in
`git log --oneline`, in a blame, in a release note, in a bisect. It has one
job — say what the code is about.
**The test.** Read the subject alone, with no diff and no body. Can you say
which part of the system it concerns, and what is now different about it? If
not, it is not finished.
**Name the thing, then what changed about it.** The symptom, the quoted error
and the metaphor are EVIDENCE, and evidence belongs in the body. A subject
built on them reads well and tells the next reader nothing:
| Instead of | Write |
|-----------|-------|
| `[FIX] nettoyage : les enfants s'en vont avec leur rebond` | `[FIX] nettoyage : les entrées ssh qui rebondissent par une VM effacée` |
| `[FIX] proxmox : « il manque le stockage » était le symptôme, pas la cause` | `[FIX] proxmox : signaler pmxcfs à terre, et non « aucun stockage »` |
| `[FIX] migration: un module fautif n'emporte plus tout le lot` | `[FIX] migration: isoler l'échec d'un module dans la désinstallation` |
The scope is not the subject. `proxmox` says WHERE; the words after the colon
must say WHAT. A subject that works with its scope removed is usually the
right one.
**Summarise the whole commit, not its largest piece.** When the work has two
faces — a guard moved and the check that proves it, a screen and the service
under it — the subject covers both or the commit should have been two. If the
only honest subject needs an `and` joining two unrelated things, split it.
**It must be complete in 72 characters.** A subject cut mid-phrase by
`--oneline` has failed at the one place it is read most. Write it to fit
rather than trimming it afterwards: drop the adjectives, keep the nouns.
When the work genuinely will not fit in a sentence, do not write an amputated
one — write **keywords that summarise**. A comma-separated list of the nouns
that matter says more in the space than half a sentence does:
```
[FIX] proxmox : pmxcfs à terre, pvesm muet, diagnostic à la source
[ADD] migration : copies de site, index doublés, réglages perdus
```
That form is a fallback, not a default. Prefer the sentence when it fits.
**The guard rail.** `script/git/hooks/commit-msg` refuses a subject with no
tag, one over 72 characters, and one opening on a quotation. It reads the body
too: over ten lines for one language, an IP address, an e-mail, and a
`/home/<account>/` path. Install the hook with `git config core.hooksPath script/git/hooks`; `git commit
--no-verify` passes a legitimate exception. It checks only what is mechanical
— whether the subject says what the code is about, and whether the body tells
the story instead of the mechanism, stay judgements, and the tests above are
how you make them.
### Keep it short
The body answers one question: why was this necessary. Stop once it is
answered — the reader owes you nothing beyond that.
**Eight lines per language. Ten is the ceiling.** Past that, the reasoning
belongs in `tasks/`, which is not versioned, and the commit points at it —
never in a code comment: a comment says how the code WORKS, not what happened
the week it was written. The budget is per language: bilingual doubles
everything, so it buys terseness, it does not excuse length.
Cut, in this order:
- Anything the diff already says. `adds function X` is visible; `X because
the DHCP lease can be stale` is not.
- Headings and bullet lists. If the change really needs sections, it needs
several commits.
- Every clause that would not change what a reader does: no `this commit`,
no `I decided to`, no summary of the summary, no restating the subject.
Keep, always: the failure mode the change removes, the figure that bounds it,
and one line naming what you verified and how. A single `Checked: 4 jobs,
1.63 s at parallelism 1 vs 0.58 s at 4` is worth three paragraphs of prose.
Two tests decide what survives, and they apply to every sentence of the body.
**Tense and subject.** Each sentence says what the code now does or refuses,
in the present. A sentence whose subject is an incident, a session, a machine,
a date or a person is cut — including your own reasoning: no `my conclusion
was wrong`, no `three faults found by running it`. That belongs in `tasks/`.
**Nothing identifying.** No customer or third-party organisation, no real
database name, no VM or host name, no IP address, no e-mail, no path carrying
a user name, no label or figure taken from a customer's data. Generalise to
the CLASS of situation instead of censoring: `on a production database`, `on a
development VM`, `on a host that demands interactive sudo` — the class is what
serves the reader; the name never was. A figure that is a durable limit or
threshold stays (65 536 inotify watches); a reading taken during one incident
goes.
### Bilingual body
Every AI-assisted commit carries its body twice. Write it first in whichever
language you were thinking in, then the marker, then the translation.
The marker names the language of what FOLLOWS it: `--- FR ---` after an
English body, `--- EN ---` after a French one. One marker per commit, never
both.
Translate, do not re-summarise: a reader of either language must get the same
reasoning, the same measured figures and the same caveats.
### The Assisted-by trailer
`AI_POLICY.md` makes this binary — there was AI involvement or there was not,
with no threshold to judge. Anything from a single suggestion to fully
autonomous coding means the trailer, and it says nothing about the quality of
the work.
- One `Assisted-by:` line per model. A session that switched models declares
each of them, one line each.
- NEVER name an AI in `Co-authored-by:`: authorship of a work by a machine is
legally undefined. That field is for other HUMANS who worked on the change.
You are already the author, so never co-author yourself.
- No blank line between trailers.
### Rules
- Subject: imperative mood, **72 characters maximum**, aim for 50.
- `scope` is the Odoo technical module (`sale_order`, `account`, `stock`) or
the area of the repository (`script todo`, `qemu ssh`, `migration`).
- The commit stands on its own: state what was verified, and how. If a claim
was not checked, say so rather than implying it was. Standing on its own
means it needs no OTHER COMMIT to be understood — not that it carries the
whole investigation. The line budget above still binds.
- If you cannot explain and defend every line, do not commit it.
### Size and pace
A patch under 30 lines in a single file is the reference point. Past ~500
lines the policy asks for prior agreement with a maintainer — say so instead
of committing quietly. When several unrelated modules are touched, propose
splitting into separate commits before writing anything.
### Execute
The timezone comes from the system, as it should: nothing is forced here, so
each contributor's commits carry their own zone. If yours land at `+0000`,
the machine itself is on UTC — common on a server or a VM — and the fix
belongs there, `sudo timedatectl set-timezone <Area/City>`, because it
affects every commit and not just this one.
The identity is passed explicitly with `-c`, which sets the author AND the
committer. `--author` alone sets only the author, and a checkout with no
configured `user.email` then fails on the committer.
Use a heredoc rather than `-m`: a body with quotes, backticks or accented
characters survives it unharmed.
```bash
git add -A
git -c user.name="Your Name" -c user.email="your@email.com" commit -F - <<'MSG'
[TAG] scope: description
Explain WHY here.
--- FR ---
The same body, translated.
Assisted-by: {MODEL}
MSG
```