erplibre/conf/template_claude_commands_todo_generate_code.md
Mathieu Benoit 2435f0f6d3 [ADD] script todo : /todo_generate_code, effort high et règles du dépôt
Les règles à appliquer avant de coder sont éparpillées entre les configs,
les hooks et les modules maison, et la documentation en contredit
plusieurs : autopep8 est recommandé alors que le script sort en 1, aucun
script oca-* n'est installé, la racine n'a ni .pre-commit-config.yaml ni
.pylintrc, et flake8 comme pylint-odoo ne vivent que dans le venv Odoo,
que rien ne lance. Le gabarit énonce ce que l'outillage impose, à effort
high, l'épinglage ultracode restant celui de l'utilisateur.

Vérifié : 146 règles relevées, 142 confirmées contre leur citation, 4
retirées. Deux gardes lient chaque entrée du menu à un gabarit présent
qui déclare le bon nom.

--- EN ---

The rules to apply before coding are scattered across the configs, the
hooks and the in-house modules, and the documentation contradicts several
of them: autopep8 is recommended although the script exits 1, no oca-*
script is installed, the root carries neither .pre-commit-config.yaml nor
.pylintrc, and flake8 as well as pylint-odoo live only in the Odoo venv,
which nothing invokes. The template states what the tooling enforces, at
high effort, the ultracode pin remaining the user's own.

Checked: 146 rules surveyed, 142 confirmed against their citation, 4
dropped. Two guards tie each menu entry to a template that exists and
declares the right name.

Assisted-by: Claude Opus 5
2026-09-02 07:16:23 -04:00

10 KiB

name description disable-model-invocation effort allowed-tools
todo_generate_code Write code in ERPLibre by the rules the repository actually enforces: Odoo/OCA module conventions first, then the real format and verify toolchain. true high
Read
Edit
Write
Grep
Glob
Bash(make format:*)
Bash(make test_unit:*)
Bash(make test_unit_file:*)
Bash(./script/maintenance/:*)
Bash(python3:*)
Bash(git status:*)
Bash(git diff:*)
Bash(ls:*)
Bash(cat:*)

Context

  • Odoo series in this checkout: !cat .odoo-version
  • Available venvs: !ls -d .venv.* 2>/dev/null
  • Branch and pending work: !git status --porcelain
  • Addons trees: !ls -d odoo*/addons/*/ 2>/dev/null | head -12

Task

Write code in this repository by the rules it ENFORCES, which are not always the rules it documents. Establish them first, then write. The sections below were read off the configuration files and the in-house modules, not off the prose; where a document disagrees with a tool, the tool wins and the disagreement is named.

The effort tier

The effort: high above applies to this invocation — "comprehensive implementation with extensive testing", the tier for code that has to survive review.

A session can be pinned above it. /effort ultracode sets xhigh AND turns on dynamic workflow orchestration, and a system-reminder then asks for a workflow on every substantive task. That standing opt-in does not apply here: writing one module correctly is one agent's job, and fanning it out multiplies both the token cost and the ways the pieces disagree.

So when a reminder says ultracode is on, say in one line that this command works at high, and ask the user to type /effort high — that one command sets the tier and clears the ultracode flag in the same move. A command's frontmatter cannot release a session pin; only the user's own /effort can, from an interactive terminal. Work solo either way.

1. Before writing a line

Find the tree. New modules go under odoo<VERSION>/addons/<Org>_<repo>/<module>/ — the version-prefixed tree is the real addons root, and the generator rewrites any addons/ it is handed into odoo<VERSION>/addons/ (script/code_generator/new_project.py:144-146). The version is in .odoo-version; the venv carries BOTH versions in its name, so find it with ls -d .venv.odoo* rather than composing it from memory.

Read the neighbours. Two or three in-house modules under odoo<VERSION>/addons/ERPLibre_erplibre_addons/ show the conventions in force better than any list. Copy their shape.

Bootstrap rather than hand-roll. script/code_generator/new_project.py -d <git root dir> -m <module name> creates a module; create_from_existing_module.py clones one. A module cloned from an existing one INHERITS its comments and docstrings — reread them before committing, a client or database name travels that way on its own.

Two rules bind every line you write, and no tool checks either:

  • Nothing identifying outside private/ — no customer or third-party organisation, no real database, host or VM name, no IP, e-mail or path carrying an account name. Generalise to the class of situation instead.
  • A comment says how the CODE works, in the present. A sentence whose subject is an incident, a machine, a date or a person belongs in tasks/, which is not versioned.

Never edit a generated file. FICHIER.md and FICHIER.fr.md come from FICHIER.base.md through mmg; an edit is lost at the next make doc_markdown.

2. Odoo module conventions

__manifest__.py carries at minimum name, version, author, license, category, summary, depends, data, installable. version is <series>.1.0.0 — 18.0.1.0.0 on an 18.0 checkout — and license is AGPL-3, which every in-house module uses.

The data list is ordered security/, then data/, then wizards/, then views/, with views/menu.xml last.

Non-Odoo Python requirements go in external_dependencies: {"python": [...]}, and the import is guarded in the model with try/except ImportError logging at debug level — a missing optional dependency must not break the registry.

Layout: models/, views/, security/, wizards/, data/, controllers/, i18n/, report/, tests/, static/description/. Ship static/description/icon.png.

Naming, one for one:

  • models/<model_name_with_dots_as_underscores>.py, one model per file — model devops.workspace lives in models/devops_workspace.py.
  • views/<same_name>.xml for a Model; a TransientModel's Python AND its XML both live in wizards/.
  • ir.ui.view ids: <model_underscored>_view_<type> (_view_form, _view_tree, _view_search, _view_kanban).
  • ir.actions.act_window ids: <model_underscored>_<action>_action_window; server actions: <model_underscored>_<name>_server_action.
  • res.groups go in security/<module_name>.xml — named after the module, not security.xml — and that file is listed BEFORE ir.model.access.csv.

security/ir.model.access.csv carries exactly the header id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink. Split the rows by privilege level — a read-only row for the broad group, a full-CRUD row for the administrative one — rather than one blanket row.

Every Python file opens with the AGPL licence comment; hand-written models and hooks also carry the shebang and copyright lines. A package __init__.py starts with # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl), a blank line, then imports. The root __init__.py imports subpackages on one line (from . import models, wizards) and a hook by name (from .hooks import post_init_hook); hooks live in a top-level hooks.py and are declared under the matching manifest key.

Models declare _name, _description and, when mixins are used, _inherit; the class name is the CamelCase of the model. Odoo symbols come in one grouped import — from odoo import _, api, exceptions, fields, models.

Translations go in i18n/ as <module>.pot plus one .po per locale.

3. Format — the toolchain that exists

make format before committing. It formats only what git reports as modified, added, renamed or untracked, dispatching each file by extension — so it is cheap and safe to run repeatedly. make format_all sweeps whole areas instead.

What it runs underneath, and what to match when writing by hand:

Kind Tool and settings
Python isort --profile black -l 79, then black -l 79 --preview -t py37
XML prettier + @prettier/plugin-xml, tab-width 4, print-width 120
XML under data/ same, print-width 999999999 — long data strings stay on one line
js, css, scss, html prettier, tab-width 4, print-width 120, no bracket spacing
Shell shfmt -i 2 -ci -w

black and isort run from .venv.erplibre, prettier from the repo-pinned ./node_modules/.bin — a globally installed prettier is a different version and reformats differently. Nothing under not_supported_files/ is formatted.

What NOT to run, each verified absent from this checkout rather than assumed:

  • ./script/maintenance/autopep8.sh — oca-autopep8 is not installed; the script exits 1. doc/DEVELOPMENT.base.md still recommends it; the document is stale.
  • Any oca-* console script (oca-gen-addon-readme, oca-towncrier, …) — script/OCA_maintainer-tools is checked out but never pip-installed, and its install block in install_locally_dev.sh is commented out. Run one as a module from that directory if you truly need it.
  • pre-commit run at the repository root — there is no root .pre-commit-config.yaml. ERPLibre's own hooks are hand-written Python, installed once per clone with git config core.hooksPath script/git/hooks.

A vendored OCA addon under odoo<VERSION>/addons/OCA_* carries its own .pre-commit-config.yaml: run THAT from its directory instead of the ERPLibre format scripts, so the patch matches what upstream will accept.

4. Lint

Neither linter runs on its own — no target, no hook, no CI invokes them, so running them is a deliberate act:

  • flake8 lives in the Odoo venv, not .venv.erplibre. The root .flake8 applies: max-line-length 80, max-complexity 16, select = C,E,F,W,B,B9, ignoring E203, E501 and W503 for black compatibility.
  • pylint-odoo also lives in the Odoo venv: .venv.odoo<...>/bin/pylint --load-plugins=pylint_odoo --rcfile=<file> <path>. The --rcfile is not optional — the repository root ships no .pylintrc, and the ones found under vendored trees belong to those projects.

5. Verify

Verification is entirely local: .github/ holds no workflow, so nothing catches a mistake after the fact.

Repository scripts — make test_unit is the fast gate: no PostgreSQL, no Odoo, no VM, a few seconds. While iterating on one file, make test_unit_file F=test/test_<name>.py.

A new file in test/ must declare at least one test_* function and end with if __name__ == "__main__": unittest.main() as the LAST top-level statement — the suite has its own test that enforces both, since the runner selects files by the glob test/test_*.py. A test that creates a real machine, installs a system or runs for hours goes in long_test/ instead, and undoes itself with --detruire.

An Odoo module — drop the database, then run the module's tests:

./odoo_bin.sh db --drop --database test_<module>
./test.sh -d test_<module> --db-filter test_<module> -i <module>

./test.sh is ./run.sh with ODOO_MODE_TEST=true --workers 0, which adds --test-enable --no-http --stop-after-init. For coverage, bracket that with ./.venv.erplibre/bin/coverage erase before and a combine/report after, and set ODOO_MODE_COVERAGE=true — coverage is switched on through the environment, never a CLI flag. make open_test_coverage opens the report.

To run ONE test file inside a module, install the module first, then pass --test-file=.

6. Then, and only then, commit

make format, the tests above, then /commit — which resolves the model, writes the [TYPE] portée : sujet subject under 72 characters, the bilingual body under ten lines per language, and the Assisted-by: trailer. The commit-msg hook REFUSES a message that breaks the mechanical part of that; the pre-commit hook only reports comments worth rereading and never blocks.

Stage by naming files. git add -A sweeps in private/ and tasks/, which are untracked on purpose.