blog.vfg.lol

La racine avait une maison — et son nom était déjà pris

Les documents du ménage de mes dépôts n'appartenaient à aucun dépôt. En cherchant où les ranger, j'ai découvert que le dépôt méta qui décrit exactement mon disque n'était pas celui qu'on m'avait désigné — et qu'un nom de sauvegarde était déjà occupé par un autre projet, 80 commits et zéro ancêtre commun.

TL;DR

Le ménage d'hier avait produit deux documents : une carte lisible de ma racine de dépôts, et un plan d'action autonome pour la suite. Le problème : ces deux documents n'appartenaient à aucun dépôt. Ma racine D:\GitRepos contient les projets, mais elle-même n'est pas versionnée. Un fichier posé là survit jusqu'au premier disque mort.

En cherchant où les déposer, trois choses sont sorties :

  1. Le dépôt qu'on m'avait désigné comme cible — vfgpt — n'existe pas. Ni en public, ni en privé, sous aucune graphie.
  2. Le bon dépôt, lui, existait, et son README décrivait mot pour mot mon disque : « Dossier racine — pas de code applicatif ici. Chaque projet = un repo sibling avec son propre .git. »
  3. Le nom de sauvegarde prévu pour le dernier dépôt sans filet était déjà pris — par un projet entièrement différent, avec qui il ne partage aucun ancêtre commun.

Le troisième point a failli me faire écraser 80 commits. Il mérite qu'on s'y arrête.


1. Le problème des documents orphelins

Hier, 21 worktrees ont été démontés, 2,77 Go archivés, 0 fichier supprimé. Le résultat est documenté par deux pièces :

FichierRôle
INDEX-RACINE.htmlcarte humaine de la racine : ce qui est vivant, ce qui est en chantier, ce qui est mort
PROMPT-AGENT-SUITE.mdplan autonome des 4 actions restantes, copiable tel quel dans une session d'agent

Les deux vivaient à la racine du disque. Et c'est précisément le problème : une racine non versionnée est un emplacement de transit, pas un emplacement de stockage. Ma campagne venait de produire de la connaissance durable dans un endroit qui ne survit à rien.

Il fallait donc trouver le dépôt qui décrit cet endroit — pas un dépôt qui y vit, mais celui qui en est la carte.


2. À la recherche du dépôt maison

Première piste : vfgpt. C'était le nom que j'avais en tête, et il apparaissait effectivement dans le corpus sous forme de branches : vfgpt/refacto-*, vfgpt/swarm-*.

Une vérification directe a réglé la question :

foreach ($n in @('vfgpt','vfg-pt','VFGpt','VFG-PT','workspace-vfg','GitRepos')) {
  git ls-remote --heads "https://github.com/FGDidou/$n.git" *>$null 2>&1 | Out-Null
  if ($LASTEXITCODE -eq 0) { "EXISTE  $n" } else { "absent  $n" }
}

Six graphies testées, six absences. vfgpt n'est pas un dépôt : c'est un préfixe de branche. Un nom qu'un chantier s'était donné, que j'avais promu par erreur au rang de dépôt.

Et un dossier D:\vfgpt existait, mais sur le disque du D:, hors de la racine des dépôts — et il contient Goose-win32-x64, LM Studio, LLM_Models. Un dossier d'outils LLM, pas un dépôt git. Absolument rien à voir.

Leçon n°1. Un nom qui apparaît dans un corpus n'est pas forcément un objet du monde. vfgpt apparaissait des dizaines de fois dans mes fichiers — toujours comme préfixe de branche, jamais comme dépôt. J'ai confondu la fréquence d'une chaîne avec son existence.

Le bon candidat

En balayant les remotes des 14 dépôts de la racine, un dépôt hors racine est ressorti : FGDidou/vfg-workspace — et il avait une branche docs/agent-presence-live-cleanup-plan, dont le nom sentait fortement ma campagne.

Son README commence par :

Dossier racine — pas de code applicatif ici. Chaque projet = un repo sibling avec son propre .git. Carte humaine = ce README + CLAUDE.md. Boussole = SYNTHESE-INDEX-PLAN.md.

C'est la définition exacte de D:\GitRepos. Le dépôt méta existait.

Deux précisions mesurées au passage :

  • GitHub est insensible à la casse. FGDidou/vfg-workspace et FGDidou/VFG-Workspace pointent vers le même dépôt — mêmes hashes de branches (main = 3cc0636 dans les deux graphies). Le manifeste du dépôt écrit VFG-Kit, le dossier local s'appelle vfg-kit : c'est le même objet.
  • La branche qui semblait liée ne l'était pas. Le diff de docs/agent-presence-live-cleanup-plan contre main porte sur 30 fichiers et une collision de port :4340 sur un registre temporel. « cleanup » dans un nom de branche ne garantit rien sur son contenu.

Leçon n°2. D:\GitRepos n'est pas un clone de VFG-Workspace. Le dépôt méta est versionné séparément, et ses entrées par tiers (_live/, _dev/, _prod/, _archived/) ne correspondent pas au layout à plat de la racine de mon disque. Les deux modèles coexistent. C'est une dette d'architecture, consignée comme telle dans le handoff plutôt que masquée.


3. Le dépôt que j'ai failli écraser

C'est le point important de la journée.

Le plan d'action disait, à propos du dernier dépôt sans sauvegarde :

D:\GitRepos\vfg-kit — branche master, HEAD c0a72c0, 3 commits, remote : AUCUN (vérifié), 63,1 Mo. Nom suggéré : FGDidou/vfg-kit (cohérent avec les autres dépôts de la racine).

L'affirmation « aucun remote » était juste : git remote -v sur le dossier local rend vide. Mais la conclusion — « le nom est libre, créons-le » — ne suivait pas.

Vérification :

git ls-remote --heads https://github.com/FGDidou/vfg-kit.git
ec993d20...  refs/heads/chore/cleanup-2026-06-17
c4958a72...  refs/heads/claude/tender-franklin-40df6b
10a1ed93...  refs/heads/feature/audit-mindmap-wt
cac01b95...  refs/heads/main

Le dépôt existe. Il a quatre branches. Et il ne s'agit pas de mon projet :

D:\GitRepos\vfg-kit (local)FGDidou/vfg-kit (distant)
HEADc0a72c0 (2026-09-08)cac01b9 (2026-06-30)
Commits380
Branchesmastermain + 3
Ancêtre commun—aucun
Contenupackages/realtime-server, packages/scene-kitagentique/, dispatch/, mindmap/, exports/

Le test décisif :

git merge-base HEAD FETCH_HEAD
# → fatal: no merge base

Aucune histoire partagée. Les deux projets ne se rencontrent que sur un nom.

Pourquoi c'est plus grave qu'un simple rejet

Un git push sur ce nom aurait renvoyé un non-fast-forward. Techniquement, git m'aurait protégé. Mais le scénario réel n'est pas le rejet :

  • un agent (ou moi) voit l'erreur « non-fast-forward », la lit comme une formalité de synchronisation, et conclut qu'il faut forcer ;
  • un --force sur une branche non partagée écrase 80 commits d'un projet réel ;
  • rien dans le message d'erreur ne dit « ce dépôt appartient à autre chose ». Git dit « tes historiques divergent », ce qui est vrai et trompeur : ils ne divergent pas, ils sont étrangers.

Et la cause profonde est banale : git remote -v rend vide sur un dépôt sans remote, ce qui ne dit strictement rien sur l'existence — ou l'occupation — du nom côté serveur. J'ai vérifié le local et conclu sur le distant.

Leçon n°3. Avant de nommer un dépôt, interroger le serveur, pas le dossier local. git ls-remote sur le nom visé, systématiquement, avant d'écrire le mot « nom suggéré » dans un plan. Un nom « libre » n'est jamais prouvé par l'absence de remote ; il est prouvé par le silence du serveur.

L'action 4 du plan a été réécrite en conséquence, avec la commande de contrôle, la description des deux options (nom libre, ou renommage préalable du dossier local) et une interdiction explicite de pousser avant confirmation humaine du nom.


4. Ce qui a été déposé

Trois pièces, dans le dépôt méta, sur main :

FichierRôle
docs/MENAGE-RACINE-GITREPOS-2026-09-22.mdcarte de la racine, état après campagne, corrections de l'audit du 15/09, corrigendum vfg-kit, règles opérationnelles
docs/PLAN-SUITE-RACINE-GITREPOS.mdplan autonome des 4 actions restantes : faits mesurés, commandes exactes, contrôles, garde-fous
docs/handoffs/2026-09-22-racine-gitrepos-nettoyage-worktrees.mdhandoff daté : résultat, leçons, doutes, blocages, prochaine action

Plus l'entrée de découverte : une section « Reprise d'un ménage de racine » dans le README (avec le tableau des trois pièces) et une entrée en tête de STATUS.md.

Le .gitignore du dépôt est en deny by default (* puis des exceptions), ce qui a une conséquence pratique : docs/PLAN-*.md est autorisé, docs/PROMPT-*.md ne l'est pas, et docs/audits/ n'existe pas dans la liste blanche. Le plan a donc été déposé sous le nom PLAN-SUITE-RACINE-GITREPOS.md. Contrôler l'ignore-list avant de nommer un fichier évite de committer dans le vide :

git check-ignore -q docs/PLAN-SUITE-RACINE-GITREPOS.md; $LASTEXITCODE   # 1 = trackable

Le commit a0cf972 est en ligne. Vérifié après coup par un clone neuf, pas par un git log local.


5. La leçon qui traverse tout

Ce document n'est pas un article sur le rangement. C'est un article sur la différence entre vérifier et croire qu'on a vérifié.

L'audit du 15/09, que j'avais suivi, affirmait trois choses. Les trois étaient fausses :

AffirmationRéalité mesurée
« RainbowPiano est en retard, 21 fichiers sales »Propre, 1 commit d'avance sur origin/main. Les « modifs » étaient du bruit de fins de ligne.
« BirdFoutain n'a aucun remote — risque irréversible n°1 »Le remote existe. Non touché.
« audit-externe-dataviz.html est une copie du dépôt »Hash différent (59 128 vs 59 826 o). Fichier conservé.

Et le plan que j'avais moi-même écrit hier contenait la quatrième :

AffirmationRéalité mesurée
« vfg-kit : aucun remote → créer FGDidou/vfg-kit »Le nom est occupé par un projet étranger de 80 commits.

Le dénominateur commun est toujours le même : une mesure juste, une conclusion qui déborde. « 21 fichiers sales » était mesuré — l'inférence « donc en retard » ne l'était pas. « Aucun remote » était mesuré — l'inférence « donc le nom est libre » ne l'était pas.

C'est exactement la même erreur que les 11 worktrees d'hier : git status disait « modifié », c'était mesuré, et l'inférence « il y a du travail ici » était fausse. Seul git diff --numstat — une mesure du contenu — a tranché.

Un chiffre vrai ne rend pas vraie la phrase qui le suit.

C'est la raison d'être de la table de correspondance ajoutée au plan d'action : chaque fait affirmé y est adossé à la commande qui l'a produit. Non pas pour faire joli, mais pour que le prochain lecteur puisse refaire la mesure au lieu de me croire. Un plan qui se contente d'affirmer est un plan qui périme ; un plan qui cite ses commandes est un plan qui se revérifie.


6. Ce qui reste

Quatre actions, dans cet ordre — la seule destructive en dernier :

#ActionRisque
1Supprimer la coquille vide TermuxWorkspacebloqué — verrou IDE à lever
2Déplacer Interface et designs in-game\ (14 fichiers, 420,8 Ko) vers docs/assets/design-2a-handoffmodéré — le bundle est externe et son HANDOFF-AGENT.md interne est périmé (il décrit « 7 couloirs, 1 octave » quand la cible est 14 couloirs / 2 octaves)
3Purger l'archive (2 766,5 Mo)élevé — irréversible, accord nominatif cible par cible
4Sauvegarder vfg-kit sous un nom librefaible — mais le nom reste à choisir par l'humain

L'action 2 mérite une mention : sans notice de catalogue, un futur agent implémenterait les mauvais chiffres en toute bonne foi, en suivant fidèlement un document que son auteur croyait à jour. Un README périmé est plus dangereux qu'une absence de README.


Conclusion

Hier, j'ai retiré 21 worktrees et archivé 2,77 Go sans rien jeter. Aujourd'hui, j'ai rangé la documentation de ce travail là où elle survit — et j'ai évité d'écraser un dépôt de 80 commits.

Le fil, c'est que la vérification n'est jamais finie. Elle a un périmètre, et ce périmètre est toujours plus étroit qu'on ne le croit. git remote -v vide prouve qu'un dossier local n'a pas de remote. Il ne prouve pas qu'un nom est disponible.

Les documents sont en ligne. Le plan est autonome. La prochaine mesure appartient à quelqu'un d'autre — et elle pourra me contredire.

source markdown

Concepts de cet article

worktreehandoff

Partage le plus de concepts avec