Commit graph

1349 commits

Author SHA1 Message Date
625288fd4c [ADD] qemu diagnostic : un relevé à transmettre, Gérer scindé en trois
Un problème de VM se résout souvent par quelqu'un qui n'a pas accès à la
machine : vingt et une sondes en lecture écrivent un relevé unique —
hôte, hyperviseur, GPU, outils, stockage — chacune bornée dans le temps,
une commande qui pend ne devant pas retenir le rapport. Sur le pilote
NVIDIA propriétaire, trois conditions vivent hors des briques Mesa et
QEMU, dont la liste de périphériques où libvirt n'ajoute jamais les nœuds
de la carte : le relevé les nomme et propose la commande qui installe ce
qui lui manque, affichée avant la question. Gérer se scinde en trois.

--- EN ---

A VM problem is often solved by someone with no access to the machine:
twenty-one read-only probes write one report — host, hypervisor, GPU,
tools, storage — each time-bounded, since a command that hangs must not
hold the report. On the proprietary NVIDIA driver three conditions live
outside the Mesa and QEMU pieces, among them the device list where
libvirt never adds the card's own nodes: the report names them and offers
the command installing what it lacks, shown before the question is put.
The Manage menu splits in three.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
416ed42448 [ADD] qemu manage : retirer la 3D quand le démarrage échoue sur EGL
Le nœud de rendu peut exister sans qu'EGL y démarre : QEMU refuse alors
le domaine, et la 3D écrite dans la définition rend la VM inutilisable
jusqu'à ce que quelqu'un défasse le réglage. La création avait déjà son
repli, le réglage d'une VM existante n'en avait pas. Un relevé accompagne
le refus, car deux briques distinctes entrent en jeu : egl-headless ouvre
le nœud par GBM — c'est Mesa qui répond — et virglrenderer ne sert
qu'ensuite, donc l'installer ne répare pas un EGL qui refuse.

Vérifié : 11 tests, rougis par trois mutations — rattraper n'importe quel
échec, ne pas redémarrer après le retrait, éteindre l'autostart au passage.

--- EN ---

The render node can exist without EGL starting on it: QEMU then refuses
the domain, and the 3D written into the definition leaves the VM unusable
until someone undoes the setting. Creation already had its fallback,
adjusting an existing VM had none. A report comes with the refusal, since
two distinct bricks are involved: egl-headless opens the node through GBM
— Mesa answers — and virglrenderer only serves afterwards, so installing
it repairs no EGL that refuses.

Checked: 11 tests, turned red by three mutations — catching any failure,
not restarting after removal, switching autostart off along the way.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
83c0d705c1 [FIX] qemu stats : un « datetime » de trop faisait tomber le menu
Le module importe la CLASSE datetime, pas le module : « datetime.datetime »
n'existe donc pas, et l'écran des statistiques sortait sur une
AttributeError qui emportait TODO entier. La garde couvre aussi
TypeError : un horodatage absent vaut None dans un résumé sans aucune
installation, et une statistique ne doit pas décider de la survie du menu.

Vérifié : un horodatage valide rend sa date, None et une valeur hors
bornes rendent « ? » sans lever.

--- EN ---

The module imports the datetime CLASS, not the module: « datetime.datetime »
therefore does not exist, and the statistics screen exited on an
AttributeError that took the whole of TODO with it. The guard also covers
TypeError: an absent timestamp is None in a summary with no install at
all, and a statistic must not decide whether the menu survives.

Checked: a valid timestamp renders its date, None and an out-of-range
value render « ? » without raising.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
12cef44991 [FIX] qemu menu : sortir le venv du PATH des outils système
Le « bin » du venv est en tête du PATH de chaque commande lancée par le
menu, et il contient un python3. Un outil système écrit en Python et
amorcé par « env python3 » s'y amorce donc, dans un interpréteur où les
modules de la distribution n'existent pas : l'import échoue sur un module
que la machine possède pourtant. Sous sudo le piège était invisible, sudo
réinitialisant le PATH ; le retirer là où il ne servait plus l'a mis au
jour.

Vérifié : 6 tests, rougis par trois mutations. La ligne d'amorçage des
outils visés n'a pas été inspectée : le mécanisme est démontré, pas qu'il
soit la cause sur un hôte donné.

--- EN ---

The venv's « bin » leads the PATH of every command the menu launches, and
it holds a python3. A system tool written in Python and started through
« env python3 » therefore boots on that interpreter, where the
distribution's modules do not exist: the import fails on a module the
machine does have. Under sudo the trap was invisible, sudo resetting the
PATH; removing it where it was no longer needed brought it out.

Checked: 6 tests, turned red by three mutations. The shebang of the tools
concerned was not inspected: the mechanism is demonstrated, not that it
is the cause on any given host.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
f016eefce9 [ADD] qemu menu : récupérer des fichiers dans le disque d'une VM
Une VM qui ne démarre plus garde ses fichiers : libguestfs monte son
qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la
manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt
son système de fichiers, la lire ne risque rien. Un arrêt reste proposé
et non imposé — une lecture vivante voit un état peut-être incohérent,
un fichier à moitié écrit, un journal non rejoué. Le nom du paquet suit
la distribution, celui de Debian n'existant nulle part ailleurs.

Vérifié : 16 tests, rougis par quatre mutations — perdre le « --ro »,
donner le nom Debian partout, dupliquer un numéro, retirer l'entrée.

--- EN ---

A VM that no longer boots still holds its files: libguestfs mounts its
qcow2 without it. Every command carries « --ro », and that is what
changes the operation: opening a running machine's disk for writing
corrupts its filesystem, reading it risks nothing. A shutdown stays
offered, not imposed — a live read sees a possibly torn state, a
half-written file, an unreplayed journal. The package name follows the
distribution, Debian's existing nowhere else.

Checked: 16 tests, turned red by four mutations — losing « --ro », using
Debian's name everywhere, duplicating a number, dropping the entry.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
bf114ac182 [FIX] script todo : une clé i18n dupliquée écrasait un libellé du menu
La section du menu des plugins prenait « Install » pour clé, déjà portée
par l'entrée « 📦 Installation » du menu principal. Dans un littéral de
dict Python la dernière définition gagne, sans erreur ni avertissement :
le menu principal affichait donc « Installer ». La clé de la section est
renommée, la première étant en place depuis longtemps.

Vérifié : le menu principal retrouve « 📦 Installation », et un balayage
des clés ne laisse que trois doublons, tous à valeurs identiques donc
sans effet.

--- EN ---

The plugin menu's section used « Install » as its key, already held by
the main menu's « 📦 Installation » entry. In a Python dict literal the
last definition wins, with no error and no warning: the main menu
therefore showed « Installer ». The section's key is renamed, the first
one having been there far longer.

Checked: the main menu gets « 📦 Installation » back, and a sweep of the
keys leaves only three duplicates, all with identical values and so
without effect.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
3bd9f1edad [FIX] qemu menu : nommer l'URI libvirt, sinon la liste des VM est vide
Sans « --connect », un virsh non root vise qemu:///session : un
hyperviseur SÉPARÉ, où aucune VM du système n'existe. « list --all » y
rend une liste vide, sans erreur ni avertissement. L'URI par défaut de
root masquait l'omission tant que les commandes passaient par sudo ;
appartenir au groupe libvirt donne le droit d'atteindre qemu:///system
mais ne change pas l'URI. Les 40 appels locaux passent donc par un
constructeur unique — dont 19 en liste d'arguments, qui gardaient encore
sudo en dur.

Vérifié : une garde balaie les deux fichiers et échoue si un virsh est
écrit sans URI ; la réintroduire fait rougir.

--- EN ---

Without « --connect », a non-root virsh targets qemu:///session: a
SEPARATE hypervisor, where none of the system's VMs exist. « list --all »
returns an empty list there, with no error and no warning. Root's default
URI masked the omission as long as commands went through sudo; libvirt
group membership grants the right to reach qemu:///system but does not
change the URI. All 40 local calls therefore go through one builder —
19 of them argument lists that still hardcoded sudo.

Checked: a guard sweeps both files and fails if a virsh is written
without the URI; putting one back turns it red.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
f417a9ea86 [ADD] qemu deploy : case 3D à la création, même sans écran virtuel
Une VM sans console peut vouloir un virtio-gpu accéléré — rendu hors
écran, ou émulateur qui tourne dedans — et « auto » ne l'accorde jamais :
il s'abstient sans écran, pour ne pas poser un périphérique vidéo que
personne n'a demandé. La case envoie donc « --gpu on », qui l'accorde
désormais. « --graphics none » est alors écarté : il dit « aucun
affichage », et egl-headless EST un affichage. Le repli le rend quand la
3D tombe, sinon la VM repartirait sur le défaut de virt-install.

Vérifié : 8 tests neufs et 2 sur le repli, rougis par trois mutations.
Le rendu de la case dans le terminal plein écran n'est pas testé.

--- EN ---

A VM without a console may want an accelerated virtio-gpu — offscreen
rendering, or an emulator running inside — and « auto » never grants it:
it abstains without a screen, so as not to add a video device nobody
asked for. The box therefore sends « --gpu on », which now grants it.
« --graphics none » is then dropped: it means « no display », and
egl-headless IS a display. The fallback gives it back when 3D fails,
otherwise the VM would restart on virt-install's default.

Checked: 8 new tests and 2 on the fallback, turned red by three
mutations. The box's rendering in the full-screen terminal is not tested.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
c8d76f1c33 [ADD] qemu deploy : proposer d'effacer un disque orphelin qui bloque
Une création interrompue laisse son qcow2 sans VM définie, et deploy_qemu
refuse ensuite d'écraser : la création échoue APRÈS avoir fait attendre.
Le disque est donc proposé à l'effacement, taille et chemin affichés.
Proposé et non effacé d'office : le même nom peut désigner le disque
d'une VM retirée à la main, dont on voulait garder les données. Un refus
ne laisse pas filer vers l'échec, il redemande. La proposition vient
après la fermeture du formulaire plein écran, parce qu'effacer là demande
root et qu'une invite de mot de passe n'y a nulle part où s'afficher.

Vérifié : 7 tests, rougis par deux mutations — ne pas redemander après un
refus, et sauter la proposition sur le chemin plein écran.

--- EN ---

An interrupted creation leaves its qcow2 with no defined VM, and
deploy_qemu then refuses to overwrite: creation fails AFTER the wait. The
disk is therefore offered for deletion, size and path shown. Offered, not
deleted outright: the same name may designate the disk of a VM removed by
hand, whose data was meant to be kept. A refusal does not drift into
failure, it asks again. The offer comes after the full-screen form
closes, because deleting there needs root and a password prompt has
nowhere to appear inside it.

Checked: 7 tests, turned red by two mutations — not asking again after a
refusal, and skipping the offer on the full-screen path.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
9f94316ab0 [FIX] qemu deploy : recréer la VM sans 3D quand EGL ne démarre pas
Le nœud de rendu existe mais EGL ne s'y initialise pas : QEMU s'arrête
sur « eglInitialize failed » pendant la connexion au moniteur. La
détection ne voit qu'un fichier dans /dev/dri, et rien ne distingue un
GPU utilisable d'un nœud sans pile EGL avant que QEMU n'essaie. La
création réessaie donc sans la 3D, après avoir retiré le domaine de
l'essai raté — sans quoi le nom reste pris. « --gpu on » n'est pas
rétrogradé en silence, et un échec qui n'est pas celui d'EGL n'est pas
rattrapé.

Vérifié : 8 tests et trois mutations. Le chemin réel n'a pas été exécuté,
faute de /dev/dri et de virt-install sur la machine de développement.

--- EN ---

The render node exists but EGL will not initialise on it: QEMU stops on
« eglInitialize failed » while connecting to the monitor. Detection only
sees a file under /dev/dri, and nothing separates a usable GPU from a
node without an EGL stack until QEMU tries. Creation therefore retries
without 3D, after undefining the domain of the failed attempt — the name
would otherwise stay taken. « --gpu on » is not silently downgraded, and
a failure that is not EGL's is not caught.

Checked: 8 tests and three mutations. The real path was not exercised,
for lack of /dev/dri and virt-install on the development machine.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
feb6cd7b12 [FIX] qemu menu : passer par le groupe libvirt plutôt que par sudo
Appartenir au groupe libvirt suffit à joindre qemu:///system : le sudo
écrit en dur n'y ajoutait aucun droit et réclamait un mot de passe à
chaque entrée de menu. La question se tranche en ESSAYANT, jamais en
lisant /etc/group : les groupes d'un processus sont figés à l'ouverture
de session, donc la table dit le déclaré, l'essai le faisable. C'est la
distinction que porte aussi l'avertissement d'avant-installation. Un
hyperviseur distant garde sudo, ses droits ne se sondant pas d'ici.

Vérifié : 10 tests, rougis par deux mutations — lire /etc/group, et
conclure « pas de sudo » sur un sondage mort.

--- EN ---

Membership of the libvirt group is enough to reach qemu:///system: the
hardcoded sudo added no right there and asked for a password at every
menu entry. The question is settled by TRYING, never by reading
/etc/group: a process's groups are frozen at session start, so the table
states what is declared, the attempt what is doable. The pre-install
warning carries that same distinction. A remote hypervisor keeps sudo,
its rights not being probeable from here.

Checked: 10 tests, turned red by two mutations — reading /etc/group, and
concluding « no sudo » from a dead probe.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
d3e8d50953 [FIX] qemu deploy : montrer le message de l'outil quand une VM échoue
Une VM ratée n'affichait que ses quatre dernières lignes, et l'épilogue
« Échec de la commande » avec sa ligne de commande les occupe entièrement :
le message de l'outil tombait juste au-dessus de la fenêtre. La sortie
étant jetée après la boucle, passé l'écran elle n'existait plus nulle
part. Trente lignes en cas d'échec, quatre en cas de réussite, et la
sortie complète va dans un fichier dont le chemin s'affiche. Quand la
commande portait la 3D, le rapport nomme « --gpu off » : le menu ne
l'expose pas, c'est la seule issue depuis là.

Vérifié : 7 tests, dont un nom de VM hostile qui ne doit pas composer un
chemin hors du répertoire de session.

--- EN ---

A failed VM showed only its last four lines, and the « Échec de la
commande » epilogue with its command line fills them entirely: the tool's
own message fell just above the window. The output being discarded after
the loop, past the screen it existed nowhere at all. Thirty lines on
failure, four on success, and the whole output goes to a file whose path
is printed. When the command carried 3D, the report names « --gpu off »:
the menu does not expose it, and it is the only way out from there.

Checked: 7 tests, among them a hostile VM name that must not compose a
path outside the session directory.

Assisted-by: Claude Opus 5
2026-09-03 05:00:55 -04:00
97ccce1def Merge branch 'develop_claude_skills'
Improve claude skills with TODO
2026-09-03 05:00:30 -04:00
d92797d38a [REF] claude : alléger le contexte, deux règles passent en skills
Environ 1 130 tokens étaient chargés à chaque session pour du contenu
dérivable ou écrit deux fois : les deux tableaux d'index décrivaient des
fichiers déjà chargés en entier, et la ligne de format de commit
contredisait 04-code-conventions.md et le hook qui l'applique. Les règles
du générateur et du déploiement ne servent qu'à une tâche : elles se
chargent désormais à la demande, comme les deux skills déjà en place.
L'interdit du générateur, lui, reste dans CLAUDE.md — une interdiction ne
doit pas dépendre de l'ouverture d'une skill.

Vérifié : aucun renvoi vers les fichiers déplacés, et les quatre skills
déclarent un nom qui correspond à leur répertoire.

--- EN ---

About 1,130 tokens loaded every session for content that is derivable or
written twice: the two index tables described files already loaded in
full, and the commit-format line contradicted 04-code-conventions.md and
the hook enforcing it. The code-generator and deployment rules serve one
task each: they now load on demand, like the two skills already in place.
The generator's prohibition stays in CLAUDE.md — a ban must not depend on
a skill being opened.

Checked: no reference points at the moved files, and all four skills
declare a name matching their directory.

Assisted-by: Claude Opus 5
2026-09-02 08:04:15 -04:00
29b12c8ca1 [ADD] qemu arch : yay et bash-completion sur l'invité, guide au MOTD
Une image cloud Arch est nue : ni bash-completion, ni accès à l'AUR. Les
deux arrivent avec l'amorçage, sur la seule branche pacman. yay-bin
plutôt que yay, dont le paquet source compile Go pour le même outil ; la
construction reste sous l'utilisateur de la VM, makepkg refusant root. Le
« || true » qui ferme le bloc porte : le groupe est le dernier membre de
sa liste « || », donc set -e s'y applique et un sudo en échec emporterait
l'installation entière. Le guide de connexion n'annonce yay que si une
installation a eu lieu.

Vérifié : 10 tests, dont « bash -n » sur la commande distante entière et
la survie du bloc sous set -e avec un PATH vide.

--- EN ---

An Arch cloud image is bare: no bash-completion, no AUR access. Both come
with the bootstrap, on the pacman branch alone. yay-bin rather than yay,
whose source package compiles Go for the same tool; the build stays under
the VM user, makepkg refusing root. The « || true » closing the block
carries weight: the group is the last member of its « || » list, so set -e
applies inside it and one failing sudo would take the whole install down.
The login guide announces yay only when an install ran.

Checked: 10 tests, among them « bash -n » over the whole remote command
and the block surviving set -e with an empty PATH.

Assisted-by: Claude Opus 5
2026-09-02 08:04:05 -04:00
736ab3b676 [FIX] qemu setup-host : demander avant de redémarrer l'hôte
Accepter d'installer les paquets QEMU redémarrait la machine sans autre
question : --assume-yes couvrait le gestionnaire de paquets, et la
commande y ajoutait --reboot-if-needed, qui ne demandait rien. Une seule
constante servait la VM qu'on vient de créer et le poste qui la crée.
--reboot-if-needed PROPOSE désormais, sur /dev/tty pour rester visible
quand la sortie est un tuyau, et vaut non par défaut ; un refus laisse
les paquets posés et dit quoi faire. --assume-yes-reboot est le seul
consentement muet, que seul le profil invité porte.

Vérifié : 7 tests, rougis par deux mutations — assume_yes rouvrant la
porte, le menu hôte reprenant le drapeau.

--- EN ---

Accepting the QEMU package install rebooted the machine with no further
question: --assume-yes covered the package manager, and the command
added --reboot-if-needed, which asked nothing. One constant served both
the VM just created and the workstation creating it. --reboot-if-needed
now OFFERS, on /dev/tty so it stays visible when output is a pipe, and
defaults to no; a refusal leaves the packages in place and says what to
do. --assume-yes-reboot is the only silent consent, carried by the guest
profile alone.

Checked: 7 tests, turned red by two mutations — assume_yes reopening the
door, the host menu taking the flag back.

Assisted-by: Claude Opus 5
2026-09-02 07:38:11 -04:00
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
4f85a4f225 [ADD] script todo : /todo_plan_max, planifier avant d'ajouter une entrée
Ajouter une entrée commence par des décisions qu'aucun gabarit
n'imposait : quel menu parent, motif A ou B, quoi faire en cas d'échec,
ce qui est détruit, ce qui touche à une donnée client. La commande les
demande avant d'écrire, planifie avec superpowers quand le plugin est
là, et pose sa spécification dans tasks/, non versionné. Son frontmatter
fixe l'effort à max, seule valeur de l'énumération ; ultracode est un
épinglage de session que l'utilisateur tape, non un réglage de gabarit.

Vérifié : les deux gabarits déployés dans un HOME jetable, relus « à
jour » par l'écran de contexte.

--- EN ---

Adding an entry starts with decisions no template forced: which parent
menu, pattern A or B, what to do on failure, what gets destroyed, what
touches customer data. The command asks them before writing, plans with
superpowers when the plugin is there, and lays its specification in
tasks/, which is not versioned. Its frontmatter sets effort to max, the
only value in the enumeration; ultracode is a session pin the user
types, not a template setting.

Checked: both templates deployed into a throwaway HOME, read back as up
to date by the context screen.

Assisted-by: Claude Opus 5
2026-09-02 06:35:57 -04:00
6847891c1b [ADD] script todo : gérer les plugins Claude Code et leur liste ERPLibre
Rien ne posait un plugin depuis TODO ; la CLI seule le faisait, hors du
menu. L'installation passe par « -y » : la sortie de TODO est un tuyau,
pas un terminal, et la CLI refuse sans lui toute installation qui
exécute une commande déclarée par un marketplace. La liste préférée
s'affiche donc AVANT la confirmation, seule occasion de la lire ; ses
quatre plugins travaillent sur le poste, sans service tiers ni compte.
La recherche lit les manifestes sur le disque, donc hors ligne.

Vérifié : 8 tests neufs, dont la frontière de mot qui sépare deux noms
dont l'un contient l'autre — une recherche naïve en rate trois.

--- EN ---

Nothing installed a plugin from TODO; the CLI alone did, outside the
menu. Installing goes through « -y »: TODO's output is a pipe, not a
terminal, and without it the CLI refuses any install that runs a
command declared by a marketplace. The preferred list is therefore
shown BEFORE the confirmation, the only chance to read it; its four
plugins run on the workstation, with no third party and no account.
Search reads the manifests from disk, hence offline.

Checked: 8 new tests, among them the word boundary separating two names
where one contains the other — a naive search misses three of them.

Assisted-by: Claude Opus 5
2026-09-02 06:35:05 -04:00
c32e7d87c9 [ADD] script todo : déployer /git_prepare_merge depuis le menu Claude
Une fusion apporte plusieurs commits d'un coup, et rien ne guidait les
deux écrits qu'elle demande : l'entrée de changelog et le message de
merge. La commande déployée impose la source — CHANGELOG.base.md, les
fichiers générés étant perdus au prochain doc_markdown — et le corps
bilingue avec son trailer, que le garde-fou ne verra jamais, commit-msg
ignorant tout message ouvrant sur « Merge ». Elle prépare, elle ne
fusionne pas : le message part dans tasks/, non versionné.

Vérifié : 45 tests du menu, et un déploiement dans un HOME jetable que
l'écran de contexte relit « à jour » face à son gabarit.

--- EN ---

A merge lands several commits at once, and nothing guided the two pieces
of writing it needs: the changelog entry and the merge message. The
deployed command imposes the source — CHANGELOG.base.md, the generated
files being lost at the next doc_markdown — and the bilingual body with
its trailer, which the guard rail never sees, commit-msg skipping any
message opening on « Merge ». It prepares, it does not merge: the
message goes to tasks/, which is not versioned.

Checked: 45 menu tests, and a deployment into a throwaway HOME that the
context screen reads back as up to date against its template.

Assisted-by: Claude Opus 5
2026-09-02 06:15:43 -04:00
8bdbf42a31 [ADD] script todo : installer Claude Code et opencode, PATH garanti
Ces installateurs posent leur binaire dans un répertoire du HOME que le PATH
d'un shell ne porte pas toujours : sans la ligne d'export, le binaire est là
et la commande reste introuvable. Le répertoire diffère d'un outil à l'autre,
et l'un des deux écrit déjà sa propre ligne — la présence se teste donc sur
le répertoire, pas sur la graphie, et rien n'est ajouté deux fois.

Le PATH d'un processus est figé depuis son démarrage : un nouveau shell est
nécessaire, ce que la sortie dit. Troisième écrivain dans le fichier de
shell, starship compris, d'où l'écriture mise en commun. Vérifié : 31 tests.

--- EN ---

These installers put their binary in a HOME directory that a shell's PATH
does not always carry: without the export line, the binary is there and the
command stays not found. The directory differs from one tool to the other,
and one of the two already writes its own line — presence is therefore tested
on the directory, not on the spelling, and nothing is added twice.

A process PATH is frozen since its start: a new shell is needed, which the
output says. Third writer into the shell file, starship included, hence the
shared write. Checked: 31 tests.

Assisted-by: Claude Opus 5
2026-09-02 04:51:03 -04:00
371605b90a [IMP] script todo : une icône par entrée du menu Git et Shell
Sept entrées sans repère visuel se lisent une par une. L'icône entre dans la
traduction, qui porte déjà le libellé affiché.

Un glyphe à présentation texte occupe une colonne quand les autres en
occupent deux, et décale la colonne des libellés : il prend deux espaces,
comme 🖥 et ⚙ ailleurs dans le fichier. Rendu vérifié dans les deux langues.

--- EN ---

Seven entries with no visual marker are read one by one. The icon goes into
the translation, which already carries the displayed label.

A text-presentation glyph takes one column where the others take two, and
shifts the label column: it gets two spaces, like 🖥 and ⚙ elsewhere in the
file. Rendering checked in both languages.

Assisted-by: Claude Opus 5
2026-09-02 04:43:43 -04:00
b321052888 [ADD] script todo : Starship depuis le menu Git, renommé Git et Shell
Poser le binaire ne change pas le prompt : c'est la ligne d'initialisation
dans le fichier du shell qui le fait, et les deux étapes échouent séparément.
Le paquet vient de la distribution quand elle le connaît, de l'installateur
amont sinon — starship n'est pas empaqueté partout. Le fichier du shell n'est
demandé que devant plusieurs candidats, et la ligne ne s'écrit qu'une fois.

L'entrée porte sa destination dans « method » : son rang suit le nombre
d'entrées de todo.json, qu'un numéro codé en dur ignorerait. Vérifié : 18
tests.

--- EN ---

Laying down the binary does not change the prompt: the init line in the shell
file does, and the two steps fail separately. The package comes from the
distribution when it knows it, from the upstream installer otherwise —
starship is not packaged everywhere. The shell file is only asked for when
several candidates exist, and the line is written only once.

The entry carries its destination in « method »: its rank follows the number
of todo.json entries, which a hard-coded number would ignore. Checked: 18
tests.

Assisted-by: Claude Opus 5
2026-09-02 04:43:25 -04:00
d31ca9eab9 [UPD] qemu manage : nom de VM sans la version d'une publication continue
Une distribution en publication continue n'a qu'une version, latest : le
segment ne distingue aucune VM d'une autre et sort du nom. Une version
nommée qui coexiste avec d'autres au catalogue y reste, tumbleweed comme
les numérotées.

Le nom se relit dans l'autre sens pour retrouver (distro, version) et
filtrer les outils par distribution : une VM déjà déployée sous l'ancien
nom n'y est plus résolue, la renommer suffit. Vérifié : catalogue rejoué
sans collision, aller-retour du nom, 6 tests.

--- EN ---

A rolling-release distribution has a single version, latest: the segment
tells no VM apart from another and leaves the name. A named version that
coexists with others in the catalogue stays, tumbleweed as much as the
numbered ones.

The name is read back the other way to recover (distro, version) and filter
tools by distribution: a VM already deployed under the old name no longer
resolves there, renaming it is enough. Checked: catalogue replayed without
collision, name round trip, 6 tests.

Assisted-by: Claude Opus 5
2026-09-02 04:13:48 -04:00
93ffb3b7e9 [ADD] script todo : poser merge.conflictStyle=zdiff3 depuis le menu Git
zdiff3 fait figurer la base commune dans les marqueurs de conflit et sort
de la zone contestée les lignes que les deux côtés ont en commun : il reste
moins à arbitrer à la main. Le style demande git 2.35, que toutes les
plateformes supportées dépassent.

La valeur est relue après écriture, « git config » ne rendant rien à
l'écriture. L'entrée est déclarée dans TestGitMenuNumbering : le menu Git
mêle entrées codées en dur et entrées de todo.json, qu'un rang de plus
décale. Vérifié : 117 tests au vert.

--- EN ---

zdiff3 puts the merge base into the conflict markers and lifts out of the
contested area the lines both sides share: less is left to arbitrate by
hand. The style needs git 2.35, which every supported platform exceeds.

The value is read back after writing, as « git config » returns nothing on
write. The entry is declared in TestGitMenuNumbering: the Git menu mixes
hard-coded entries with todo.json ones, which one more rank shifts.
Checked: 117 tests green.

Assisted-by: Claude Opus 5
2026-09-02 04:01:10 -04:00
3dcccf40d9 [FIX] script todo : rtk hors du PATH, résultat d'installation annoncé
Un processus garde le PATH qu'il avait au démarrage : rtk installé dans
~/.local/bin pendant que TODO tourne échappe à shutil.which, et « rtk » nu
sort en 127. Le menu le cherche donc aussi à l'emplacement de
l'installateur et l'appelle par son chemin absolu, sans confondre un
binaire hors PATH avec une absence.

L'installation annonce son résultat : version et chemin, ou échec.
Vérifié : 9 tests neufs, 117 au total.

--- EN ---

A process keeps the PATH it had at startup: rtk installed into
~/.local/bin while TODO runs escapes shutil.which, and a bare « rtk »
exits 127. The menu therefore also looks at the installer's location and
calls the binary by its absolute path, without mistaking a binary outside
the PATH for a missing one.

Installation now reports its outcome: version and path, or failure.
Checked: 9 new tests, 117 in total.

Assisted-by: Claude Opus 5
2026-09-02 03:53:40 -04:00
1cc74c83b4 [FIX] qemu shrink : place mesurée avant sauvegarde, étape annoncée
La sauvegarde double la place occupée et le défaut était OUI : sur un
disque presque plein, une entrée vide lançait une copie qui s'arrête à
mi-course et laisse un .bak tronqué. Les deux tailles passent donc avant
la question, et le défaut bascule à NON quand la place manque.

Le besoin annoncé est la taille ALLOUÉE : « cp --sparse=always » ne
recopie pas les trous d'un qcow2. La sortie du gestionnaire de paquets
enchaînait par ailleurs sur une question portant sur autre chose ;
l'étape se referme d'une ligne.

Vérifié : le défaut remis à OUI sans place, comme la taille apparente au
lieu de l'allouée, font tomber les tests. 4144 verts.

--- EN ---

A backup doubles the space used and the default was YES: on a nearly
full disk, an empty answer started a copy that stops midway and leaves a
truncated .bak. Both sizes now come before the question, and the default
flips to NO when the room is short.

The need shown is the ALLOCATED size: "cp --sparse=always" does not copy
the holes of a qcow2. The package manager output also ran straight into a
question about something else; the step now closes with a line of its
own.

Checked: putting the default back to YES without room, like the apparent
size instead of the allocated one, makes the tests fail. 4144 green.

Assisted-by: Claude Opus 5
2026-08-31 07:51:19 -04:00
87ad8c4439 [ADD] script todo : installer les outils manquants, les quatre familles
La réduction sûre listait les outils manquants et s'arrêtait là. Trois
écritures séparées savaient installer, chacune un sous-ensemble
différent : openSUSE posait virt-viewer, mais ni navigateur CLI ni
lm-sensors.

Le binaire n'est presque jamais le paquet : sgdisk vit dans « gdisk »
chez Debian et Fedora, dans « gptfdisk » chez Arch et openSUSE. Deux
règles passent aussi dans la composante : l'ID de la distribution décide
avant le PATH, et la commande s'affiche avant la question.

Vérifié : commandes identiques sur les trois familles déjà couvertes,
4139 tests verts.

--- EN ---

The safe shrink listed the missing tools and stopped there. Three
separate writings knew how to install, each covering a different
subset: openSUSE could put virt-viewer down, but neither a CLI browser
nor lm-sensors.

A binary is almost never the package: sgdisk lives in "gdisk" on Debian
and Fedora, in "gptfdisk" on Arch and openSUSE. Two rules move into the
component as well: the distribution ID decides before the PATH, and the
command is shown before the question.

Checked: commands identical on the three families already covered, and
4139 tests green.

Assisted-by: Claude Opus 5
2026-08-31 07:18:13 -04:00
40a1e178f3 [FIX] qemu manage : ni adresse, ni nom de VM, ni récit en commentaire
Les commentaires suivent le dépôt en amont : une adresse ou un nom de
machine qui s'y trouve devient public. Cinq adresses et deux noms de VM
y étaient, plus cinq phrases racontant la séance où le défaut est apparu.

Le mode de défaillance reste, au présent — « une VM renommée se voit
attribuer la passerelle » — et l'incident part. Un relevé d'un seul jour
part avec lui ; la structure durable reste.

check_comment_hygiene.py passe de 5 identifiants et 5 à relire à zéro.
Aucune ligne de code n'est touchée, seulement du texte de commentaire.

--- EN ---

Comments follow the repository upstream: an address or a machine name
sitting in one becomes public. Five addresses and two VM names were
there, plus five sentences telling the session where the fault appeared.

The failure mode stays, in the present — "a renamed VM is given the
gateway" — and the incident goes. A reading taken on one day goes with
it; the durable structure stays.

check_comment_hygiene.py goes from 5 identifiers and 5 to re-read down to
zero. No line of code is touched, only comment prose.

Assisted-by: Claude Opus 5
2026-08-31 07:17:22 -04:00
0b8f25fb4b [ADD] script todo : installer les hooks git depuis le menu Git
Un commit-msg non installé, c'est le garde-fou de convention qui ne tourne
jamais, et la pose restait une commande à recopier. git saute sans rien
dire un hook privé du bit d'exécution : l'installation le remet.

exec_command_live ne passe aucun cwd et ce dépôt porte 128 dépôts
imbriqués : sans « git -C racine », un lancement depuis un addon y écrivait
core.hooksPath et laissait la racine sans garde-fou. Et comme cette
fonction retourne le code sans jamais lever, un « fatal: not in a git
directory » annonçait « hooks installés ».

Vérifié : 4114 tests verts ; le nouveau garde tombe sur un dispatch
échangé comme sur une entrée sans branche.

--- EN ---

An uninstalled commit-msg is the convention guard never running, and
installing it stayed a command to copy by hand. git skips a hook without
the execution bit and says nothing: installing restores it.

exec_command_live passes no cwd and this checkout holds 128 nested
repositories: without the "git -C root", a launch from an addon wrote
core.hooksPath there and left the root unguarded. And since that function
returns the exit code without ever raising, a "fatal: not in a git
directory" still announced "hooks installed".

Checked: 4114 tests green; the new guard fails on a swapped dispatch as
well as on an entry with no branch.

Assisted-by: Claude Opus 5
2026-08-31 06:17:48 -04:00
dfd6cd1fb6 Merge branch 'force_convention_commit'
[ADD] commentaires : la convention, ses deux garde-fous, le code nettoyé

Six commits. Le dépôt n'avait aucune règle sur le commentaire de code : le
seul modèle d'écriture disponible était celui du corps de commit, et le
gabarit y renvoyait explicitement le raisonnement qui n'y tenait pas. Le
récit s'est donc écoulé dans les sources, avec les noms qu'il portait.

L'épreuve tient en une phrase : le code est le sujet, au présent de ce qu'il
fait. Une phrase dont le sujet est un incident, une machine, une date ou une
personne part vers tasks/, qui n'est pas versionné ; le mode de défaillance
que le code empêche, lui, reste. Rien d'identifiant hors de private/, et
l'exemple qui illustre cet interdit s'invente plutôt qu'il ne s'emprunte,
un test figeant pour toujours ce qu'il contient.

Deux garde-fous, de sévérité opposée. Le hook commit-msg REFUSE la part
mécanique d'un message : longueur par langue, adresse, courriel, chemin de
compte. Le hook pre-commit INFORME — il liste les commentaires à relire dans
les fichiers indexés et sort toujours en 0, un contrôle bloquant à cette
échelle se faisant désinstaller. L'outil sépare la trouvaille du signal :
l'identifiant se retire, le récit se relit, la même phrase pouvant énoncer
un fait durable.

Un écran du menu TODO relève le contexte fourni à un assistant sur ses six
sources et repère une commande déployée qui a dérivé de son gabarit. Code et
jeux d'essai perdent au passage leurs valeurs réelles — base, adresse,
compte, hôte — au profit de valeurs inventées.

Vérifié : 65 tests neufs, dont 43 pour l'outil seul. Le prédicat qui
distingue une adresse d'une version de manifeste est partagé par les deux
garde-fous, deux prédicats pour une même question finissant par diverger.

--- EN ---

Six commits. The repository had no rule at all about code comments: the only
writing model available was the commit body, and the template explicitly sent
the reasoning that did not fit there into a code comment. The story therefore
flowed into the sources, carrying the names it named.

The test fits in one sentence: the code is the subject, in the present of
what it does. A sentence whose subject is an incident, a machine, a date or a
person goes to tasks/, which is not versioned; the failure mode the code
prevents stays. Nothing identifying outside private/, and the example
illustrating that ban is invented rather than borrowed, a test freezing
forever what it holds.

Two guard rails, of opposite severity. The commit-msg hook REFUSES what is
mechanical in a message: length per language, address, e-mail, account path.
The pre-commit hook INFORMS — it lists the comments worth re-reading in the
staged files and always exits 0, a blocking check at that scale getting
uninstalled. The tool separates the finding from the signal: identifying data
is removed, narrative is re-read, the same sentence being able to state a
durable fact.

A TODO screen reports the context given to an assistant across its six
sources and spots a deployed command that drifted from its template. Code and
fixtures lose their real values along the way — database, address, account,
host — for invented ones.

Checked: 65 new tests, 43 of them for the tool alone. The predicate telling an
address from a manifest version is shared by both guard rails, two predicates
for one question ending up diverging.

Assisted-by: Claude Opus 5
2026-08-31 00:54:23 -04:00
54f44755cd [I18N] commit-msg : le garde-fou parle la langue du dépôt
Les messages du garde-fou étaient des littéraux français : l'anglais n'était
pas supporté, quelle que soit la valeur de EL_LANG. Onze clés passent
désormais par t(), pour 6 ms de surcoût mesuré par commit. L'en-tête
annonçait « sujet » alors que le contrôle lit aussi le corps ; il dit
« message ».

Les tests ne passaient que parce que le dépôt est en français : un poste en
anglais en cassait quarante. La langue devient une précondition — épinglée en
mémoire, car set_lang() écrirait un fichier suivi — et les assertions qui
citent du texte français se sautent en nommant leur raison.

Vérifié en basculant EL_LANG : 48 tests OK en français, 2 sautés en anglais.

--- EN ---

The guard rail's messages were French literals: English was not supported at
all, whatever EL_LANG said. Eleven keys now go through t(), for a measured
6 ms of overhead per commit. The header announced "subject" while the check
reads the body too; it says "message".

The tests passed only because the repository is in French: a workstation in
English broke forty of them. The language becomes a precondition — pinned in
memory, since set_lang() would write a tracked file — and the assertions
quoting French text skip while naming their reason.

Checked by switching EL_LANG: 48 tests OK in French, 2 skipped in English.

Assisted-by: Claude Opus 5
2026-08-31 00:50:31 -04:00
fcad37c24f [FIX] suite unitaire : bit d'exécution, script/*.py balayé, clé i18n
Trois contrôles préexistants passaient au rouge sur cette branche.

Un fichier qui porte un shebang doit être exécutable : commité en 644, il
casse deux invariants, le mode stocké par git et le bit sur disque. Ses six
frères de script/analyse/ sont en 755.

Le contrôle d'outillage ne balayait pas script/*.py : un module partagé posé
là passait pour un paquet tiers non déclaré. Trois fichiers de plus entrent
dans le balayage, sans révéler d'autre dépendance. Et une clé de traduction
appelée six fois n'était pas déclarée, l'écran rendant un mot anglais.

Vérifié : 4105 tests, tout vert.

--- EN ---

Three pre-existing checks were turning red on this branch.

A file carrying a shebang must be executable: committed as 644, it breaks two
invariants, the mode git stores and the bit on disk. Its six siblings in
script/analyse/ are 755.

The tooling check never scanned script/*.py: a shared module placed there was
taken for an undeclared third-party package. Three more files enter the scan,
revealing no other dependency. And a translation key called six times was
undeclared, the screen rendering an English word.

Checked: 4105 tests, all green.

Assisted-by: Claude Opus 5
2026-08-31 00:03:50 -04:00
01a3ba25ca [UPD] changelog : les 32 commits du merge migration et de la branche
Le journal s'arrêtait à la fusion précédente : trente-deux commits n'y
figuraient pas, l'outillage de migration d'un côté, la convention d'écriture
et ses garde-fous de l'autre.

Dix-sept entrées les couvrent, en Ajouté, Modifié, Corrigé et Retiré. Le
`.base.md` est édité et mmg régénère les deux fichiers dérivés, qui ne se
modifient jamais à la main.

Vérifié : autant de puces en français qu'en anglais dans chaque section, et
aucune donnée identifiante. `[Unreleased]` porte deux sections Modifié et
deux Corrigé, défaut antérieur ; les entrées vont dans les premières.

--- EN ---

The changelog stopped at the previous merge: thirty-two commits were missing
from it, the migration tooling on one side, the writing convention and its
guard rails on the other.

Seventeen entries cover them, across Added, Changed, Fixed and Removed. The
`.base.md` is edited and mmg regenerates the two derived files, which are
never edited by hand.

Checked: as many French bullets as English ones in every section, and no
identifying data. `[Unreleased]` carries two Changed and two Fixed sections,
a pre-existing defect; the entries go into the first of each.

Assisted-by: Claude Opus 5
2026-08-30 06:14:05 -04:00
4701660203 [UPD] convention : nommer les fichiers à l'indexation, pas git add -A
`git add -A` indexe tout ce qui n'est pas suivi. Or `private/` et `tasks/` ne
le sont pas EXPRÈS : le premier est le seul endroit autorisé à porter une
donnée de client, le second reçoit l'enquête précisément parce qu'il ne part
pas dans git. Un ratissage les commit tous les deux.

Il emporte aussi ce qui est en cours ailleurs dans le checkout, sous un sujet
qui ne le couvre pas. Le gabarit nomme désormais les fichiers et renvoie à
`git add -p` quand un fichier porte deux sujets.

La règle porte la même consigne : le gabarit ne se lit qu'à l'appel de la
commande, la règle est chargée à chaque session.

--- EN ---

`git add -A` stages everything untracked. Yet `private/` and `tasks/` are
untracked ON PURPOSE: the first is the only place allowed to carry customer
data, the second receives the investigation precisely because it does not go
into git. A sweep commits both.

It also swallows whatever else is in flight in the checkout, under a subject
that does not cover it. The template now names the files and points at
`git add -p` when one file carries two subjects.

The rule carries the same instruction: the template is read only when the
command is invoked, the rule is loaded every session.

Assisted-by: Claude Opus 5
2026-08-30 06:14:05 -04:00
78e702e1e3 [ADD] todo : afficher le contexte fourni à Claude
Le contexte d'un assistant est éparpillé sur six sources — instructions,
règles, skills, commandes déployées, hooks git, mémoire — et rien ne disait
laquelle manquait ni laquelle avait dérivé.

L'écran les relève. Chaque commande déployée est comparée à son gabarit du
dépôt en ignorant les lignes d'identité git, que le déploiement substitue :
une copie périmée se voit, là où une égalité stricte les déclarerait toutes
périmées. Les termes de la liste privée sont COMPTÉS, jamais affichés.

--- EN ---

An assistant's context is scattered over six sources — instructions, rules,
skills, deployed commands, git hooks, memory — and nothing said which one was
missing nor which one had drifted.

The screen reports them. Each deployed command is compared to its repository
template, ignoring the git identity lines the deployment substitutes: a stale
copy shows up, where a strict equality would declare them all stale. The
private list's terms are COUNTED, never displayed.

Assisted-by: Claude Opus 5
2026-08-30 06:14:05 -04:00
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
7844387fa1 [ADD] commit-msg : contrôler le corps, sa longueur et l'identifiant
Le garde-fou ne lisait que le sujet : le corps portait des noms de bases
réelles, des adresses et des chemins de compte, et sa longueur médiane
faisait une fois et demie le plafond de la convention.

Il lit désormais le corps, trailers et diff de --cleanup=scissors exclus :
longueur par langue, adresse IP, courriel, chemin de compte, et les termes
d'une liste qui vit hors du dépôt puisque c'est elle qu'elle protège —
absente, le contrôle se tait. Le prédicat qui sépare une adresse d'une
version de manifeste est partagé ; 40 tests couvrent les deux lectures.

--- EN ---

The guard rail read the subject only, while the body carried real database
names, addresses and account paths, and its median length was one and a
half times the convention's ceiling.

It now reads the body, trailers and the --cleanup=scissors diff excluded:
length per language, IP address, e-mail, account path, and the terms of a
list living outside the repository, since the list is what it protects —
absent, that check stays silent. The predicate telling an address from a
manifest version is shared; 40 tests cover both readings.

Assisted-by: Claude Opus 5
2026-08-30 05:11:36 -04:00
5fb49da0f2 [ADD] convention : dire le fonctionnement, pas le contexte ni les noms
Le dépôt n'avait aucune règle sur le commentaire de code : le seul modèle
d'écriture était le corps de commit, et le gabarit y renvoyait le
raisonnement qui n'y tenait pas. Le récit s'écoulait dans les sources,
avec les noms qu'il portait.

L'épreuve tient en une phrase : le code est le sujet, au présent de ce
qu'il fait. Une phrase dont le sujet est un incident, une machine, une date
ou une personne part vers tasks/, non versionné ; le mode de défaillance
que le code empêche reste. Rien d'identifiant hors de private/, et
seulement sur un dépôt privé.

--- EN ---

The repository had no rule at all about code comments: the only writing
model available was the commit body, and the template sent the reasoning
that did not fit there into a comment. The story flowed into the sources,
carrying the names it named.

The test fits in one sentence: the code is the subject, in the present of
what it does. A sentence whose subject is an incident, a machine, a date or
a person goes to tasks/, unversioned; the failure mode the code prevents
stays. Nothing identifying outside private/, and only on a private
repository.

Assisted-by: Claude Opus 5
2026-08-30 05:11:36 -04:00
960918c939 Merge branch 'migration_anonymisation_pricelist'
[ADD] migration : copie anonymisée, verdicts remontés, dérives dépistées

Vingt-deux commits. Une copie de production s'ouvre désormais sans rien
atteindre au dehors : `exp_duplicate_database` d'Odoo coupe la source,
régénère l'uuid, emporte le filestore et neutralise — depuis la 16 seulement.
En deçà, un script du dépôt prend le relais plutôt que de refuser, et
l'exécution annonce lequel a servi, car les deux ne posent pas les mêmes
gestes.

L'anonymisation passe sa liste noire entière — le SQL par un fichier, un
argument ne portant que 131 072 octets — et n'épargne plus les noms qu'elle
devait masquer. Elle laisse intacts les chemins d'identifiants qu'Odoo
repasse à `int()`, et tire ses nombres dans l'étendue mesurée de leur
colonne : la fiche masquée s'ouvre encore.

Une migration ratée ne passe plus pour réussie. Les verdicts de chaque
palier, de la 12 à la 18, remontent dans un écran de qualité, avec le passage
de journal qui les explique et la sortie des outils telle qu'ils l'écrivent :
un pseudo-terminal la capture sans faire taire les invites conditionnées par
`isatty`, ANSI retiré et secrets caviardés. Un code 1 y dit des trouvailles,
non un échec.

Trois dérives muettes deviennent dépistables : un dépôt d'addons absent d'un
palier, un type de vue supprimé qui survit dans des sources qu'aucune base ne
montre, un plan comptable qui gonfle sous un gabarit rechargé.

La liste de prix cesse d'être dupliquée à vide : la décision se prend sur ce
que `base.group_user` implique et non sur les groupes de l'exécutant, et une
liste partagée entre sociétés compte enfin pour chacune. Le reste tient la
chaîne : `odoo_bin.sh` lit le config.conf du dépôt par ODOO_RC, les bases se
nomment sans collision, le registre se précharge, le lanceur de tests
n'oublie plus de fichier, et un hook tient le sujet des commits.

--- EN ---

Twenty-two commits. A production copy now opens without reaching anything
outside it: Odoo's `exp_duplicate_database` cuts the source, regenerates the
uuid, carries the filestore over and neutralises — from 16 onwards only.
Below it a repository script takes over rather than refusing, and the run
announces which one served, because the two do not make the same gestures.

Anonymisation runs its whole blacklist through — the SQL by way of a file, a
single argument carrying no more than 131,072 bytes — and no longer spares
the names it was meant to mask. It leaves alone the identifier paths Odoo
passes back through `int()`, and draws its numbers from their own column's
measured range: the masked record still opens.

A failed migration no longer passes for a successful one. Every step's
verdicts, 12 through 18, surface in a quality screen, with the log passage
that explains them and the tools' output as they wrote it: a pseudo-terminal
captures it without silencing the prompts conditioned on `isatty`, ANSI
stripped and secrets redacted. There, exit code 1 means findings, not
failure.

Three silent drifts become detectable: an addons repository missing from one
step, a removed view type surviving in sources no database shows, a chart of
accounts swelling under a reloaded template.

The pricelist stops being duplicated empty: the decision is taken on what
`base.group_user` implies rather than on the runner's groups, and a pricelist
shared across companies at last counts for each of them. The rest holds the
chain: `odoo_bin.sh` reads the repository's config.conf through ODOO_RC,
databases are named without collision, the registry is preloaded, the test
runner no longer skips a file, and a hook holds commit subjects.

Assisted-by: Claude Opus 5
2026-08-30 05:07:03 -04:00
17295fd7c3 [ADD] migration : mesurer la dérive du plan comptable entre paliers
Une montée de version recharge le gabarit de localisation sans rien dire :
trois des scripts l10n du noyau appellent try_loading() sans
force_create=False, et tout compte que le gabarit n'apparie pas par code
est CRÉÉ. Les groupes ajoutés reclassent alors le plan existant.

Un compte absolu ne dit rien : seul l'ÉCART entre deux paliers de la même
migration se juge — comptes sans écriture, noms homonymes, grille apparue
de rien. L'outil ne répare pas : la clé naturelle de suppression attrape
aussi des comptes réappariés par code, et des ajoutés sont accrochés en ON
DELETE SET NULL. Il dit de rejouer le palier ; 24 tests le couvrent.

--- EN ---

A version bump reloads the localisation template silently: three of the
core l10n scripts call try_loading() without force_create=False, and every
account the template cannot match by code is CREATED. The groups thus
added then reclassify the existing chart.

An absolute count says nothing: only the GAP between two steps of one
migration can be judged — accounts with no entry, homonymous names, a
grouping grid from nothing. The tool does not repair: its natural deletion
key also catches accounts re-matched by code, and added ones are pinned by
ON DELETE SET NULL. It says to replay the step; 24 tests cover it.

Assisted-by: Claude Opus 5
(cherry picked from commit e99575967e0ad5b5122a2c35df3b9eaef7a72d53)
2026-08-29 02:11:04 -04:00
d2832f5c60 [FIX] migration 17→18 : retirer la vue morte account_root
Odoo 17 créait une vue SQL pour account.root ; en 18 le modèle porte
_auto = False et _table_query = '0' : son nom n'entre plus dans aucune
requête, et le contrôle des tables manquantes ignore ces modèles.

Elle n'est pas seulement morte, elle est FAUSSE : bâtie sur la colonne code que
l'ORM 18 n'écrit plus, elle rend trop peu de racines. Elle est aussi l'unique
épingle des deux colonnes héritées où database_cleanup échoue, d'où ce retrait
avant le chargement en 18 ; aucun DROP COLUMN, OpenUpgrade les lit encore.

Sur base jetable, l'ordre est rejouable et laisse intactes une vue d'une
autre forme, une vue dont dépend un objet, une table homonyme.

--- EN ---

Odoo 17 created a SQL view for account.root; in 18 the model carries
_auto = False and _table_query = '0': its name no longer enters any query,
and the missing-table check ignores such models.

It is not merely dead, it is WRONG: built on the code column the 18 ORM no
longer writes, it returns too few roots. It is also the sole pin holding the
two legacy columns database_cleanup fails on, hence its removal before the
load into 18; no DROP COLUMN, OpenUpgrade still reads them afterwards.

On a throwaway database the statement replays cleanly and leaves alone a
view of another shape, a view something depends on, a table of that name.

Assisted-by: Claude Opus 5
(cherry picked from commit b50e2308cbdd1efdfa914497062919eab1bbd329)
2026-08-29 02:11:04 -04:00
809273eb34 [FIX] verdicts de migration : distinguer trouvaille et échec d'outil
L'écran peignait en échec tout code non nul, quand la convention écrite
dans todo_upgrade.run_tool dit : 0 rien à signaler, 1 des trouvailles, 2
l'outil a échoué. database_cleanup imprime lui-même « This is a warning,
not a failure » avant de rendre 1.

Un écran qui contredit l'outil apprend à ignorer les deux : du rouge
signalait une migration en échec là où rien n'avait échoué.

L'icône rejoint la couleur dans migration_status, et le panneau écrit le
sens du chiffre à côté de lui : « statut 1 (des trouvailles) ». Testé.

--- EN ---

The screen painted every non-zero code as a failure, where the convention
written in todo_upgrade.run_tool says: 0 nothing to report, 1 findings, 2
the tool failed. database_cleanup itself prints "This is a warning, not a
failure" before returning 1.

A screen that contradicts the tool teaches you to ignore both: red marked
a migration as failed where nothing had failed.

The icon now lives beside the colour in migration_status, and the panel
spells the number out: "status 1 (findings)". Covered by tests.

Assisted-by: Claude Opus 5
(cherry picked from commit 39d122965e7a09d710255fff061a7526a4077aaa)
2026-08-29 02:11:04 -04:00
b7d68cd199 [FIX] odoo_bin : lire le config.conf du dépôt, pas le ~/.odoorc
« odoo_bin.sh db --drop » échoue par AccessDenied à chaque palier de
migration, et le clone bute ensuite sur « database already exists ».

db_restore.py lit ./config.conf, y voit « admin_passwd = admin » et
n'envoie donc aucun mot de passe ; odoo_bin.sh ne passait pas de « -c »,
donc Odoo lisait ~/.odoorc, qui porte un mot de passe haché.

ODOO_RC ferme la couture en un point plutôt qu'à vingt sites d'appel :
les versions 12 à 18 le lisent après « -c » et avant ~/.odoorc, donc un
choix explicite l'emporte. Vérifié : le drop passe, la précédence est testée.

--- EN ---

"odoo_bin.sh db --drop" fails with AccessDenied at every migration step,
and the clone then hits "database already exists".

db_restore.py reads ./config.conf, sees "admin_passwd = admin" and so
sends no master password; odoo_bin.sh passed no "-c", so Odoo read
~/.odoorc, which carries a hashed password.

ODOO_RC closes the seam in one place instead of twenty call sites:
versions 12 to 18 read it after "-c" and before ~/.odoorc, so an explicit
choice wins. Verified: the drop succeeds, and a test covers precedence.

Assisted-by: Claude Opus 5
(cherry picked from commit 9503d80982b725034513b610b527f29d49025381)
2026-08-29 02:11:04 -04:00
7ba3c6504c [UPD] invites : 15 secondes pour décider, et le fichier nomme la base
Cinq secondes ne suffisaient pas à LIRE la question. Le compte à rebours
n'existe pas pour aller vite mais pour qu'on puisse s'absenter ; trop
court, il fait l'inverse — on répond par réflexe, ou l'on subit un défaut
non lu. Et « test » ne disait rien de ce qu'on migrait : des migrations
successives portaient toutes le même nom, quand le fichier de sauvegarde
en porte déjà un parlant. Le nom est assaini — il finit dans un createdb —
et borné à 41 caractères : le pilote ajoute « _neutralize_upgrade_18 » et
PostgreSQL tronque à 63, ce qui ferait finir deux paliers sur le même nom.
Un téléchargement distant garde celui que le serveur a donné.

--- EN ---

Five seconds were not enough to READ the question. The countdown does not
exist to go fast but so you can step away; too short, it does the
opposite — you answer by reflex, or you get a default you never read. And
"test" said nothing about what was being migrated: successive migrations
all carried the same name, when the backup file already carries a telling
one. The name is sanitised — it ends up in a createdb — and capped at 41
characters: the driver appends "_neutralize_upgrade_18" and PostgreSQL
truncates at 63, which would land two steps on one name. A remote
download keeps the name the server gave.

Assisted-by: Claude Opus 5
(cherry picked from commit dad0f6b0ef4551898fe2964e177ff5c1c888b5dc)
2026-08-29 02:11:04 -04:00
f6aa2575c3 [ADD] migration : garder la sortie des tests, par pseudo-terminal
Le journal d'étape notait la commande et son code de retour, jamais ce
qu'elle avait écrit : l'écran d'analyse ne pouvait rien montrer d'un
échec. Un tube aurait capturé et changé le programme — smoke_public_url
appelle can_ask(), qui exige stdin ET stdout sur un terminal, et derrière
un tube il cesse en silence d'offrir la réparation des vues COW. Un
pseudo-terminal lève le dilemme : l'enfant voit un vrai terminal, la
réponse tapée lui parvient, le code de retour survit. Neuf exécutions y
passent, dont check_hidden_models, qui tournait sans verdict retenu ;
deux restent dehors, pty.spawn naît en 0×0 et un plein écran s'y perdrait.

--- EN ---

The step log recorded the command and its exit code, never what it
wrote: the analysis screen could show nothing of a failure. A pipe would
have captured and changed the program — smoke_public_url calls can_ask(),
which requires stdin AND stdout to be terminals, and behind a pipe it
silently stops offering the COW view repair. A pty settles it: the child
sees a real terminal, a typed answer reaches it, the exit code survives.
Nine runs go through it, including check_hidden_models, which ran with no
verdict recorded; two stay out, pty.spawn starts at 0×0 and a full-screen
app would lay out on nothing.

Assisted-by: Claude Opus 5
(cherry picked from commit 81e9227502a0d16e6409338cf958495a5f8e3827)
2026-08-29 02:11:04 -04:00
1f606fed00 [ADD] verdicts : tous les paliers, leur journal, et la bascule
L'écran de qualité ne listait que les échecs, quand la question devant
une base migrée est « qu'a-t-on vérifié » : les quatorze verdicts
s'affichent, de la 12 à la 18, seule façon de voir qu'un échec a été
rattrapé à un palier plus haut. Le panneau ne portait que la commande ;
il montre le passage du journal d'étape qui l'entoure, garde par tee ce
qu'il lance lui-même et le relit sans relancer. La sortie de l'outil,
elle, part sur le terminal : un tube ferait renoncer les pleins écrans.
Relancer un test d'un autre palier ouvrait la base avec la mauvaise
version, qui y écrit avant d'échouer ; l'écran demande avant de basculer.

--- EN ---

The quality screen listed failures only, when the question in front of a
migrated database is "what did we check": all fourteen verdicts now show,
12 through 18, the only way to see that a failure at one tier was
recovered higher up. The panel carried only the command; it shows the
step-log passage around it, keeps by tee what it runs itself and re-reads
that without rerunning. The tool output goes to the terminal: a pipe
would make full-screen tools give up. Replaying a test from another tier
opened the database with the wrong version, which writes before it
fails; the screen asks before switching the checkout.

Assisted-by: Claude Opus 5
(cherry picked from commit 2d460b7c777d39a887dabfe7cf5405864c6c3f8c)
2026-08-29 02:11:04 -04:00
8fe0bafe10 [ADD] migration : détecter le type de vue tree resté dans les sources
Odoo 18 a supprimé le type de vue tree, sans conversion : un module
resté dessus ne s'installe pas, ou casse au clic (« View types not
defined tree found in act_window »). Les autres outils de la revue
lisent la BASE, où un module jamais installé ne laisse rien.

Le mot reste un identifiant valide : sur 465 occurrences, 80 cassent — le
noyau 18 garde account.view_invoice_tree en <list>. D'où lxml et ast,
jamais de regex : seule la position du littéral décide. Trois portes
filtrent avant tout motif : version, module installable, fichier chargé.
Balayage : 80 constats sur 2654 modules, 26 touchés.

--- EN ---

Odoo 18 removed the tree view type with no shim: a module still on it
fails to install, or breaks on click ("View types not defined tree found
in act_window"). The other review tools read the DATABASE, where a
module that never installed leaves nothing.

The word is still a valid identifier: of 465 occurrences, 80 break — the
18 core keeps account.view_invoice_tree in <list>. Hence lxml and ast,
never regex: only the literal's position decides. Three gates filter
before any pattern: version, installable module, loaded file. Sweep: 80
findings over 2654 modules, 26 affected.

Assisted-by: Claude Opus 5
(cherry picked from commit 38a91039d111b3fa6de32edf315c5caae8553c11)
2026-08-29 02:11:04 -04:00
599401b6c4 [ADD] manifestes : détecter un dépôt absent d'un palier de migration
Une migration 12 → 18 traverse six paliers, et le checkout bascule de
manifeste à chaque fois. Un dépôt d'addons absent du manifeste du 17
n'existe pas sur disque pendant l'étape 17 : Odoo déclare ses modules
introuvables, le pilote propose de les effacer, on répond oui, et la
fonctionnalité part sans qu'aucun échec ne soit signalé.

Seul le trou compte — présent avant et après, absent au milieu — et la
branche en amont le confirme : sur 35 trous, 19 sont de vraies
omissions, quinze déclarées en 16 et 18 mais pas en 17 ; les 35 auraient
fait 46 % de bruit. development.git est rétabli en 13 et en 15.

--- EN ---

A 12 → 18 migration crosses six steps, and the checkout switches
manifest each time. An addons repository missing from the 17 manifest
does not exist on disk during step 17: Odoo reports its modules as
missing, the driver offers to delete them, you answer yes, and the
feature is gone without a single failure being reported.

Only the hole counts — present before and after, absent in between — and
the upstream branch confirms it: of 35 holes, 19 are real omissions,
fifteen declared in 16 and 18 but not in 17; all 35 would have been 46%
noise. development.git is restored in 13 and in 15.

Assisted-by: Claude Opus 5
(cherry picked from commit d7613ea930d45153e9fb7c08460690a95e3dca0e)
2026-08-29 02:11:04 -04:00
33cf04c7f9 [REM] résidus : retirer le contrôle res_lang.active à NULL
Le contrôle jugeait cassée une langue dont `active` est NULL : listée
nulle part, plus réactivable. Faux dans la source 18 — un domaine
('active','=',False) compile en (IS NULL OR = FALSE) (models.py:3217),
l'action du menu Langues porte active_test: False, le tri passe par
COALESCE(active, FALSE) et la lecture rend bool(value).

Odoo écrit ce NULL : active = fields.Boolean() sans défaut
(res_lang.py:64) et un res.lang.csv sans la colonne, soit un NULL par
langue ajoutée au catalogue. « Zéro avant, non nul après » ne suffit
donc pas. Un test refuse désormais une clé que plus rien ne définit.

--- EN ---

The check called a language broken when `active` is NULL: listed
nowhere, no longer re-enablable. False in the 18 source — an
('active','=',False) domain compiles to (IS NULL OR = FALSE)
(models.py:3217), the Languages menu action carries active_test: False,
sorting goes through COALESCE(active, FALSE), reading returns bool(value).

Odoo writes that NULL itself: active = fields.Boolean() with no default
(res_lang.py:64) and a res.lang.csv without the column, one NULL per
language added to the catalogue. So "zero before, nonzero after" is not
enough. A test now refuses a key that nothing defines any more.

Assisted-by: Claude Opus 5
(cherry picked from commit ee932334cfde63c1af831316549a432c3d382721)
2026-08-29 02:11:04 -04:00