"""Confirm-loop tools: descriptor reads, name attach, merge/mark-distinct.""" from __future__ import annotations import importlib import numpy as np import pytest def _fake_embed(texts): out = [] for t in texts: v = np.zeros(64, dtype=np.float32) for w in t.lower().split(): v[hash(w) % 64] += 1.0 out.append((v if v.any() else np.full(64, 1e-6, dtype=np.float32)).tolist()) return out @pytest.fixture def mods(tmp_path, monkeypatch): monkeypatch.setenv("LYRA_DB_PATH", str(tmp_path / "test.db")) from lyra import llm monkeypatch.setattr(llm, "embed", _fake_embed) import lyra.memory as memory importlib.reload(memory) import lyra.poker as poker importlib.reload(poker) import lyra.tools as tools importlib.reload(tools) return poker, tools def test_descriptor_read_creates_then_reuses_nameless_villain(mods): poker, tools = mods poker.start_session(venue="Meadows", stakes="1/3", buy_in=300) tools.dispatch("add_read", {"note": "opened UTG light", "descriptor": "neck tattoo sleeve arm"}, {}) tools.dispatch("add_read", {"note": "showed a bluff", "descriptor": "neck tattoo sleeve"}, {}) # rephrase → same guy players = [p for p in poker.get_villain_file() if not p["named"]] assert len(players) == 1 # one nameless villain, not two reads = poker._c().execute( "SELECT COUNT(*) n FROM player_reads WHERE player_id = ?", (players[0]["id"],) ).fetchone()["n"] assert reads == 2 def test_name_villain_tool_attaches_name(mods): poker, tools = mods poker.start_session(venue="Meadows", buy_in=300) poker.create_descriptor_villain("neck tattoo sleeve arm", venue="Meadows") out = tools.dispatch("name_villain", {"descriptor": "neck tattoo sleeve arm", "name": "Danny"}, {}) assert "Danny" in out assert poker.resolve_villain("Danny")["band"] == "name" def test_link_villains_merge_and_distinct(mods): poker, tools = mods poker.start_session(venue="Meadows", buy_in=300) poker.upsert_player("Danny", venue="Meadows") poker.upsert_player("Donny", venue="Meadows") # same=false → recorded distinct tools.dispatch("link_villains", {"player_a": "Danny", "player_b": "Donny", "same": False, "note": "different builds"}, {}) a = poker.resolve_villain("Danny")["match_id"] b = poker.resolve_villain("Donny")["match_id"] assert poker.are_distinct(a, b) # same=true on a fresh pair → merged poker.upsert_player("Mike", venue="Meadows") poker.upsert_player("Michael", venue="Meadows") tools.dispatch("link_villains", {"player_a": "Mike", "player_b": "Michael", "same": True}, {}) names = [p["name"] for p in poker.get_villain_file()] assert ("Mike" in names) ^ ("Michael" in names) # one absorbed the other def test_link_villains_refuses_when_reference_is_vague(mods): poker, tools = mods poker.start_session(venue="Meadows", buy_in=300) poker.upsert_player("Danny", venue="Meadows") out = tools.dispatch("link_villains", {"player_a": "Danny", "player_b": "some guy", "same": True}, {}) assert "didn't merge" in out.lower() or "couldn't" in out.lower()