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
260 lines
10 KiB
Markdown
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
|
|
```
|