diff --git a/.claude/rules/04-code-conventions.md b/.claude/rules/04-code-conventions.md index 70b4204..4001c2f 100644 --- a/.claude/rules/04-code-conventions.md +++ b/.claude/rules/04-code-conventions.md @@ -12,4 +12,25 @@ indentations par type de fichier. - Branches : `develop` (développement), `master` (production) - Pas de submodules Git — utilise **Google Repo** pour les addons - Manifests XML dans `manifest/` pour chaque version Odoo -- Format de commit : `[TYPE] description` (ex: `[FIX]`, `[UPD]`, `[ADD]`, `[REM]`) +- Format de commit : `[TYPE] portée : sujet`, sujet à l'impératif, 72 + caractères au plus. Tags réellement utilisés : `[UPD]`, `[FIX]`, `[ADD]`, + `[IMP]`, `[REF]`. + +### Tout commit assisté par IA + +Trois exigences, sans exception — `AI_POLICY.md` en donne la raison : + +- Trailer `Assisted-by: `, une ligne par modèle. C'est **binaire** : + il y a eu IA ou non, aucun seuil à apprécier. +- **Jamais** d'IA dans `Co-authored-by:` — ce champ est réservé aux humains. +- Corps **bilingue** : le corps, puis `--- FR ---` (ou `--- EN ---`, le + marqueur nomme la langue de ce qui SUIT), puis la traduction. + +Court et direct : **10 lignes par langue**, 15 est déjà long. Le corps dit +pourquoi c'était nécessaire, puis s'arrête. Rien de ce que le diff montre +déjà ; on garde le symptôme, le chiffre mesuré et la vérification. + +Le mode d'emploi complet — résolution dynamique du modèle, gabarit, identité +git, taille des correctifs — est dans +`conf/template_claude_commands_commit.md`, déployable en `/commit` par +`TODO › Execute › GPT code › Claude configs`. diff --git a/conf/template_claude_commands_commit.md b/conf/template_claude_commands_commit.md index 2dbf0e6..e97ca07 100644 --- a/conf/template_claude_commands_commit.md +++ b/conf/template_claude_commands_commit.md @@ -1,6 +1,6 @@ --- name: commit -description: "OCA/Odoo conventional commit in English with dynamic Claude Code attribution." +description: "ERPLibre commit: OCA tag, bilingual body, Assisted-by trailer per AI_POLICY.md." disable-model-invocation: true allowed-tools: - Bash(git add:*) @@ -8,8 +8,6 @@ allowed-tools: - Bash(git commit:*) - Bash(git diff:*) - Bash(git log:*) - - Bash(claude --version) - - Bash(cat:*) - Bash(python3:*) --- @@ -19,62 +17,171 @@ allowed-tools: - Full diff: !`git diff HEAD` - Current branch: !`git branch --show-current` - Last 5 commits (for style reference): !`git log --oneline -5` -- Claude Code version: !`claude --version 2>/dev/null | head -1` ## Task -Before committing, retrieve the active model with: +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 json, os -path = os.path.expanduser('~/.claude/settings.json') -try: - d = json.load(open(path)) - print(d.get('model', 'claude-sonnet-4-6')) -except: - print('claude-sonnet-4-6') +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:]))) " ``` -Then create an OCA/Odoo-compliant commit. +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. -### OCA Tags +### Tags | Tag | Usage | |-----|-------| -| `[IMP]` | Improvement / new feature | +| `[UPD]` | Update existing code, data or configuration | | `[FIX]` | Bug fix | -| `[REF]` | Refactoring | -| `[ADD]` | New module | -| `[REM]` | Remove code/module | -| `[MOV]` | Move/rename | +| `[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] module_name: short description in imperative mood +[TAG] scope: short description in imperative mood -Explain WHY the change was made (not what — the diff already shows that). -Keep lines under 80 characters. +Explain WHY the change was made — the diff already shows what. Name the +symptom that led to it, and what was measured rather than assumed. +Wrap at 80 characters. -Generated by Claude Code {VERSION} {MODEL} +--- FR --- -Co-Authored-By: Your Name +The same body, translated. + +Assisted-by: {MODEL} ``` +### Keep it short + +The body answers one question: why was this necessary. Stop once it is +answered — the reader owes you nothing beyond that. + +**Ten lines per language. Fifteen is already long.** Past that, the reasoning +belongs in a document or a code comment, and the commit points at it. 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 symptom that led to the change, the figure you measured +rather than assumed, 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. + +### 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 -- **English only**, imperative mood, subject line under 50 chars -- Use the Odoo technical module name (e.g. `sale_order`, `account`, `stock`) -- If multiple modules are impacted, suggest splitting into separate commits +- 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. +- 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 `, 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 commit --author="Your Name " -m "[TAG] module: description +git add -A +git -c user.name="Your Name" -c user.email="your@email.com" commit -F - <<'MSG' +[TAG] scope: description Explain WHY here. -Generated by Claude Code {VERSION} {MODEL} +--- FR --- -Co-Authored-By: Your Name " +The same body, translated. + +Assisted-by: {MODEL} +MSG ``` diff --git a/script/todo/todo.py b/script/todo/todo.py index 9bc2923..c947631 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -1086,14 +1086,8 @@ class TODO: if personalize: name = input(t("Enter your full name: ")).strip() email = input(t("Enter your email: ")).strip() - content = content.replace( - "Your Name ", - f"{name} <{email}>", - ) - content = content.replace( - "Your Name ", - f"{name} ", - ) + content = content.replace("your@email.com", email) + content = content.replace("Your Name", name) os.makedirs(dest_dir, exist_ok=True) with open(dest_file, "w") as f: