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
228 lines
10 KiB
Markdown
228 lines
10 KiB
Markdown
---
|
|
name: todo_generate_code
|
|
description: "Write code in ERPLibre by the rules the repository actually enforces: Odoo/OCA module conventions first, then the real format and verify toolchain."
|
|
disable-model-invocation: true
|
|
effort: high
|
|
allowed-tools:
|
|
- 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:
|
|
|
|
```bash
|
|
./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.
|