Просмотр исходного кода

Mémoire : un surnom qui est le nom d'un autre membre est ignoré

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Johnounet 2 дней назад
Родитель
Сommit
a12c1a7d9b
3 измененных файлов с 35 добавлено и 1 удалено
  1. 11 1
      bot/memory.py
  2. 4 0
      changelog.txt
  3. 20 0
      tests/test_memory.py

+ 11 - 1
bot/memory.py

@@ -162,15 +162,25 @@ class MemoryStore:
         ce qui est noté. Tout changement de ce texte (fiche créée, pseudo, fichier général) fait
         ce qui est noté. Tout changement de ce texte (fiche créée, pseudo, fichier général) fait
         réécrire en cache tout le prompt, historique compris : voir ClaudeClient.ask."""
         réécrire en cache tout le prompt, historique compris : voir ClaudeClient.ask."""
         lines = []
         lines = []
+        # Noms Discord, anciens compris, de tous les membres du registre (fiche ou non), avec leurs
+        # propriétaires : un surnom qui est le nom d'un autre est ignoré (voir plus bas).
+        owners: dict[str, set[str]] = {}
+        for member_id, entry in self.members.members().items():
+            for discord_name in [entry[key] for key in NAME_KEYS if entry[key]] + entry["former_names"]:
+                owners.setdefault(discord_name.casefold(), set()).add(member_id)
         for member_id, entry in self.members.members().items():
         for member_id, entry in self.members.members().items():
             # Mémoire désactivée : Rika ne doit rien noter sur ce membre. Pas encore prévenu (membre
             # Mémoire désactivée : Rika ne doit rien noter sur ce membre. Pas encore prévenu (membre
             # seulement mentionné) : pas de fiche.
             # seulement mentionné) : pas de fiche.
             if not entry["memory"] or not entry["notified"] or member_id not in self.index():
             if not entry["memory"] or not entry["notified"] or member_id not in self.index():
                 continue
                 continue
             name = display_name(entry)
             name = display_name(entry)
+            # Le membre écrit ses surnoms lui-même avec /memoire : « Surnoms : Alice » ferait noter dans
+            # sa fiche ce qu'on dit d'Alice. Un surnom égal au surnom d'un autre reste : l'ignorer
+            # permettrait de faire disparaître celui de l'autre ; Claude voit alors les deux.
+            nicknames = [n for n in self.index()[member_id] if not owners.get(n.casefold(), set()) - {member_id}]
             # Sans doublon, majuscules comprises (« penta » et « Penta » : un seul nom).
             # Sans doublon, majuscules comprises (« penta » et « Penta » : un seul nom).
             others: dict[str, str] = {name.casefold(): name}
             others: dict[str, str] = {name.casefold(): name}
-            for other in [entry[key] for key in NAME_KEYS] + self.index()[member_id]:
+            for other in [entry[key] for key in NAME_KEYS] + nicknames:
                 if other:
                 if other:
                     others.setdefault(other.casefold(), other)
                     others.setdefault(other.casefold(), other)
             others = list(others.values())[1:]
             others = list(others.values())[1:]

+ 4 - 0
changelog.txt

@@ -117,6 +117,10 @@ Corrections
   perdue, ou la ligne retirée revenait. Quand un texte n'est pas
   perdue, ou la ligne retirée revenait. Quand un texte n'est pas
   enregistré (fichier changé, trop long…), il est renvoyé en fichier
   enregistré (fichier changé, trop long…), il est renvoyé en fichier
   joint pour ne pas être perdu.
   joint pour ne pas être perdu.
+- Mémoire : un membre ne peut plus prendre le nom Discord d'un autre
+  (pseudo, nom d'utilisateur ou ancien pseudo) comme surnom dans sa
+  fiche. Ce surnom est ignoré dans la liste des membres : Rika ne note
+  plus dans sa fiche ce qu'on raconte sur l'autre.
 
 
 Divers
 Divers
 - Le token Discord et les clés Anthropic et Steam n'apparaissent plus
 - Le token Discord et les clés Anthropic et Steam n'apparaissent plus

+ 20 - 0
tests/test_memory.py

@@ -79,6 +79,26 @@ class NicknamesTest(MemoryTestCase):
         self.write(f"membres/{MEMBER_ID}.md", f"Surnoms : {name}")
         self.write(f"membres/{MEMBER_ID}.md", f"Surnoms : {name}")
         self.assertEqual(self.store.nicknames(MEMBER_ID), [name])
         self.assertEqual(self.store.nicknames(MEMBER_ID), [name])
 
 
+    def member_line(self, member_id: int) -> str:
+        return next(line for line in self.store.system_context().splitlines() if f"membres/{member_id}.md" in line)
+
+    async def test_other_member_name_ignored(self):
+        # Scénario de l'audit : Bob s'attribue le nom d'Alice, qui n'a pas encore de fiche.
+        await self.members.see(222, "alice", "Alice")
+        await self.members.see(333, "carl", "Carl", nickname="Carlito")
+        await self.members.see(333, "carl", "Carl", nickname="Charly")  # Carlito devient un ancien pseudo
+        self.write(f"membres/{MEMBER_ID}.md", "Surnoms : ALICE, Toto, alice, carlito, Bob")
+        self.assertEqual(self.member_line(MEMBER_ID), f"- Bob (aussi : Toto) → membres/{MEMBER_ID}.md")
+
+    async def test_nickname_of_another_kept(self):
+        # Un surnom écrit dans deux fiches reste dans les deux : Claude voit l'ambiguïté.
+        await self.members.see(222, "alice", "Alice")
+        await self.members.set_notified(222)
+        self.write("membres/222.md", "Surnoms : Lili")
+        self.write(f"membres/{MEMBER_ID}.md", "Surnoms : Lili")
+        self.assertIn("Lili", self.member_line(222))
+        self.assertIn("Lili", self.member_line(MEMBER_ID))
+
 
 
 class SystemContextTest(MemoryTestCase):
 class SystemContextTest(MemoryTestCase):
     async def test_server_file_inside_tags(self):
     async def test_server_file_inside_tags(self):