From d7343d1be21e030f5ba441675d127bfa9e12e5af Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sat, 11 Apr 2026 19:37:39 -0400 Subject: [PATCH 1/5] wip: add updated_at, content_hash, touch() to ArchiveEntry --- .../__pycache__/entry.cpython-311.pyc | Bin 0 -> 4494 bytes nexus/mnemosyne/entry.py | 21 +++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 nexus/mnemosyne/__pycache__/entry.cpython-311.pyc diff --git a/nexus/mnemosyne/__pycache__/entry.cpython-311.pyc b/nexus/mnemosyne/__pycache__/entry.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e12425af2da70ae529b9742c7f105c48c37d46e GIT binary patch literal 4494 zcmc&%-ESLN6~8kcf5dj|=EF(qrtNH+(ha0eNW7(jCZydhEeKMUbfs#Nm6>|R@uXvq z_s+O&B9}E%B(hSqibPP0wrInu^g{))NUg*lu=@s*r?OOQ5K;u<0iL`H52#O^Ghjq*Rp%Q;P7&dZ5l z@VuDLHVhP<7x_F`@k*XIvNE65RBe%pg{+e1?K}Us@&?!x6ge&PT87WbVqVEF25%@v z78-^qiH11Jr?tER-yG$|yu_4MofA zvm>n4fxV8pWwG!{zfZcw*rk;hu!M1TM#5E z1n-5hg?-DRJw1n|POyl$7M*)6B2pAA4m2&Ku6@QAwZGc!+Vt#WbHKLgZQATJmM(iN zF|?4or9r8GmD@$29%*2e2))u_>EJ3S#N#Bjqyh|vfuJRnQ2V1Ii^=g4DbcEn?Obc0%4 zv8YIv7cu#mY-2AkN5_m@VJz2N4JU7C^x9ZnzFE|Flw zooK3ip-Ua1On&#~Gwx~d58i^zh-pKk%d`{p77Ifo zme)`WRkr+%`G^o=JPOw@a(ad`ru}H^4bmRx22+y zwmKADgJhHw4SE2Swu5LlN<9Ft@bMvdw0#7iOrBkQ`u)!bJ{vTXv(@D6i{zE($tz}3 zs3wJmx2<5B%9wFdqVa!s0zHZ|`|=YVMDt?^NE|CzEMNk}saRe+qgp;H8%3H=yI})6 z3>N&D?F|s_kP=yfyHh@b><4Yv`Xlly&!)SKnpSvk58^Gxz?e14k{k9Cvx^Xo&97S??%a5I?%2ik^SrIz5I&m*VI$mwe2^qS|d(Z1V1xtlhl zL)GZei|FX{=%^V@RHKPXAYq4b|5z2^3;*k-SRyGmJ`!Wcq5oVlSKxPeg(GsCd{t+L#H~O2K_e*9x zS&b)O#HXLfr_K0GH9oV(-Hxuowbx6SKx{_e2BDqNt!xI_jTi^ z9Gf;S!$BWt(Zd1HDWJEHeb|zidm;4T?BfuPL3@Xm^dL6l2$-noVT54>^cy{a@O6Y! z2$KMr&u(!LxGu%*s*0tD`cHA(w_Sin6_VaQH z#^W52J|GgOkP>^J`4E?cceZVPB5z-FRoC%LJkOWy?DR5t z21$_Ca)s%YueLEEL$#dE1vqkOX;Iau6Fauwf=={Nfd7<9rDx8mzr1|+?M>#>xBqyo z`pq9z-oUzkcKtm^0<`bj!0S8?Q_SBOdx~eC0HN704*6AyZk> zF?c_2CfW{n#4COK&jjDr4FDK5U?ftd)*RLQ}0!QrGkFfN0_`ZD(Ys$F!j&SzWD9VlnCV-IvE09X%#hjc)k+pV`u%f9{%ikP$Km(NwWX{VapbgiqWe7<}Mub*D3^~A~-DqPH|CKs!9_m;m?;d-5Fa=kTv zxct2eH|$iC8?Fr;zUzG?RR$)_fywH?B$`h;)#N5?vH1EskCK(xxEUL-#>UGRE8MtK zO>Vr_cW7O%j0#x$=FPtOYTrEeKJQeMoBt9P{e$bpN0%%86K4NJwSS^~zQRp7)#N6& zyq$H{;~lG$CVb%`zR2noM;m0lXPG)_!9b@Ma%wvqA-=9U>+>G1lO|vy$&aO)SiP&WYM*EEnL97sZT8BM5e|+X(>w zU>v?q(;tEf=3@OBK;6SIOr7Nzu1*lbggI6rL#=g-M9TJ6Bd=HXt2J_>vR|!{LzVVg iBcZZ=!Fp#ybvD6(oA$yNsQWhpxQ-329^7SVyY9a;oB?nE literal 0 HcmV?d00001 diff --git a/nexus/mnemosyne/entry.py b/nexus/mnemosyne/entry.py index a9e4be4..bcfc67d 100644 --- a/nexus/mnemosyne/entry.py +++ b/nexus/mnemosyne/entry.py @@ -6,6 +6,7 @@ with metadata, content, and links to related entries. from __future__ import annotations +import hashlib from dataclasses import dataclass, field from datetime import datetime, timezone from typing import Optional @@ -24,8 +25,19 @@ class ArchiveEntry: topics: list[str] = field(default_factory=list) metadata: dict = field(default_factory=dict) created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat()) + updated_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat()) links: list[str] = field(default_factory=list) # IDs of related entries + @property + def content_hash(self) -> str: + """SHA-256 hash of title + content for dedup detection.""" + raw = f"{self.title}\x00{self.content}".encode() + return hashlib.sha256(raw).hexdigest() + + def touch(self): + """Bump updated_at to now.""" + self.updated_at = datetime.now(timezone.utc).isoformat() + def to_dict(self) -> dict: return { "id": self.id, @@ -36,9 +48,16 @@ class ArchiveEntry: "topics": self.topics, "metadata": self.metadata, "created_at": self.created_at, + "updated_at": self.updated_at, "links": self.links, + "content_hash": self.content_hash, } @classmethod def from_dict(cls, data: dict) -> ArchiveEntry: - return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__}) + # Strip non-field keys (like content_hash which is computed) + filtered = {k: v for k, v in data.items() if k in cls.__dataclass_fields__} + # Backfill updated_at for legacy entries that lack it + if "updated_at" not in filtered: + filtered["updated_at"] = filtered.get("created_at", datetime.now(timezone.utc).isoformat()) + return cls(**filtered) -- 2.43.0 From 5e26ee0a7d2eafa7c4bfc247fe06aa688a5dfd65 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sat, 11 Apr 2026 19:38:17 -0400 Subject: [PATCH 2/5] wip: add update_entry, find_by_hash, find_duplicates, skip_dups to archive --- .../__pycache__/archive.cpython-311.pyc | Bin 0 -> 39787 bytes nexus/mnemosyne/archive.py | 95 +++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 nexus/mnemosyne/__pycache__/archive.cpython-311.pyc diff --git a/nexus/mnemosyne/__pycache__/archive.cpython-311.pyc b/nexus/mnemosyne/__pycache__/archive.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aee0d23d713171671fdc4feac8e9e2445b6dd688 GIT binary patch literal 39787 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
!8>*gzVWhjaaB*&G9Pg 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 ArchiveEntry: - """Add an entry to the archive. Auto-links to related entries.""" + def add(self, entry: ArchiveEntry, auto_link: bool = True, skip_dups: bool = False) -> ArchiveEntry: + """Add an entry to the archive. Auto-links to related entries. + + Args: + entry: The entry to add. + auto_link: Whether to automatically compute holographic links. + skip_dups: If True, return existing entry instead of adding a duplicate + (same title+content hash). + + Returns: + The added (or existing, if skip_dups=True and duplicate found) entry. + """ + if skip_dups: + existing = self.find_by_hash(entry.content_hash) + if existing: + return existing self._entries[entry.id] = entry if auto_link: self.linker.apply_links(entry, list(self._entries.values())) @@ -581,6 +595,83 @@ class MnemosyneArchive: self._save() return entry + def update_entry( + self, + entry_id: str, + title: Optional[str] = None, + content: Optional[str] = None, + metadata: Optional[dict] = None, + re_link: bool = True, + ) -> ArchiveEntry: + """Update fields on an existing entry. + + Only provided fields are changed. Bumps updated_at and optionally + recomputes holographic links (since content changed). + + Args: + entry_id: ID of the entry to update. + title: New title (None = keep existing). + content: New content (None = keep existing). + metadata: New metadata dict (None = keep existing, {} to clear). + re_link: Whether to recompute holographic links after update. + + Returns: + The updated ArchiveEntry. + + Raises: + KeyError: If entry_id does not exist. + """ + entry = self._entries.get(entry_id) + if entry is None: + raise KeyError(entry_id) + + old_hash = entry.content_hash + + if title is not None: + entry.title = title + if content is not None: + entry.content = content + if metadata is not None: + entry.metadata = metadata + entry.touch() + + # Re-link only if content actually changed + if re_link and entry.content_hash != old_hash: + # Clear existing links to this entry from others + for other in self._entries.values(): + if entry_id in other.links: + other.links.remove(entry_id) + entry.links = [] + # Re-apply + self.linker.apply_links(entry, list(self._entries.values())) + + self._save() + return entry + + def find_by_hash(self, content_hash: str) -> Optional[ArchiveEntry]: + """Find an entry by its content hash (title + content SHA-256). + + Returns the first match, or None if no entry has this hash. + """ + for entry in self._entries.values(): + if entry.content_hash == content_hash: + return entry + return None + + def find_duplicates(self) -> list[list[ArchiveEntry]]: + """Find groups of entries with identical content hashes. + + Returns a list of groups, where each group is a list of 2+ entries + sharing the same title+content. Sorted by group size descending. + """ + hash_groups: dict[str, list[ArchiveEntry]] = {} + for entry in self._entries.values(): + h = entry.content_hash + hash_groups.setdefault(h, []).append(entry) + dups = [group for group in hash_groups.values() if len(group) > 1] + dups.sort(key=lambda g: len(g), reverse=True) + return dups + def rebuild_links(self, threshold: Optional[float] = None) -> int: """Recompute all links from scratch. -- 2.43.0 From a896d58d931a82276c03840c6951b65e6141b4ed Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sat, 11 Apr 2026 19:39:11 -0400 Subject: [PATCH 3/5] wip: add tests for update_entry, find_duplicates, skip_dups, content_hash --- .../__pycache__/test_archive.cpython-311.pyc | Bin 0 -> 48609 bytes nexus/mnemosyne/tests/test_archive.py | 188 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 nexus/mnemosyne/tests/__pycache__/test_archive.cpython-311.pyc diff --git a/nexus/mnemosyne/tests/__pycache__/test_archive.cpython-311.pyc b/nexus/mnemosyne/tests/__pycache__/test_archive.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d83ad0a557f1dae7776369e770876d0c515e525 GIT binary patch literal 48609 zcmeHw32+=&dfxP$J-`eG7YW`R5+FfP1WE8dKvEd+lt}6zNQqoZ&=5TUh6HA)o`Fcr z0usI6WFdx$xH4nG4r9ZPR)H*5!b-Uk+O<`oK)Z@nE;~K&Bto^Lvb>4BUb_-Qrd&}} zu5!Nr_1)70fmp7@b=>Ir@%kM-J+I$?zW@F2--g3wE*#(g6FK=`&%0dzgD$kgttKA) z7Uy!k;}TqFTmmP!`?xb4`|m#E#(z(rr{8ELL*?aum*6w&;+UCXa?LYtOwj8YyjLUYy{jUv;b}wHUaJsHUsVywgB!DwgT=JwgI*Z+X43oI{^0z zI|26zy8xdQb_2Evt$;CM4`92n7jVC@5AcBCKH+M5GPj~5k;!JF-Dxp;ES2a_X9iP= zXk6^-O}>_hcBRF{`k&CdJ>JC0-e=?4Ui=TWDa8j;S#c28Rfp4k=^inDu{YUuG?}`P z5RqA_-%YLEMw1b9~;OXIRdEY~g52zM9hFwFhF;hJ)-{pD_$q)3)Va|NF^V$G*4RPhf ztoJ>#FPqHvCFDR?I+dl4@n_Q)lUbmghaZq!09$iaLBC2L*y6|pP)Qt~h)BW*m zGM#$g%?5@T!2^9+X{ky#X=qbx;y&4U=0YZVDVgoH-SW#wdpdoAzDi7VB`+p2xy6Um zm!jEpv_F0!5xp4CWaH7UxOgs|ii`0kk4Udb8LZV^>HdoY*+ge1*`Mr-¬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 HX!rjEcKz-O literal 0 HcmV?d00001 diff --git a/nexus/mnemosyne/tests/test_archive.py b/nexus/mnemosyne/tests/test_archive.py index 4a4ab61..04109bf 100644 --- a/nexus/mnemosyne/tests/test_archive.py +++ b/nexus/mnemosyne/tests/test_archive.py @@ -491,3 +491,191 @@ def test_tag_persistence_across_reload(): fresh = a2.get(e.id) assert "beta" in fresh.topics assert "alpha" not in fresh.topics + + +# --- Entry update + dedup tests --- + +def test_content_hash_deterministic(): + e1 = ArchiveEntry(title="Test", content="Hello") + e2 = ArchiveEntry(title="Test", content="Hello") + assert e1.content_hash == e2.content_hash + + +def test_content_hash_differs_on_change(): + e = ArchiveEntry(title="Test", content="Hello") + h1 = e.content_hash + e.content = "World" + assert e.content_hash != h1 + + +def test_updated_at_set_on_creation(): + e = ArchiveEntry(title="T", content="c") + assert e.updated_at == e.created_at + + +def test_touch_updates_timestamp(): + import time + e = ArchiveEntry(title="T", content="c") + before = e.updated_at + time.sleep(0.01) + e.touch() + assert e.updated_at >= before + + +def test_update_entry_title(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + e = ingest_event(archive, title="Old", content="content", topics=["x"]) + old_hash = e.content_hash + updated = archive.update_entry(e.id, title="New Title") + assert updated.title == "New Title" + assert updated.content == "content" + assert updated.updated_at >= e.created_at + # Content unchanged, so hash should be same (only title changed) + assert updated.content_hash != old_hash # title is in hash + + +def test_update_entry_content_relinks(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + e1 = ingest_event(archive, title="Python", content="Python programming language") + e2 = ingest_event(archive, title="Java", content="Java programming language") + # e1 and e2 should be linked via shared tokens + assert e2.id in e1.links or e1.id in e2.links + + # Update e1 to completely different content + archive.update_entry(e1.id, content="Cooking recipes for dinner") + e1_fresh = archive.get(e1.id) + e2_fresh = archive.get(e2.id) + # e1 should have been re-linked (likely unlinked from e2 now) + # e2 should no longer reference e1 + assert e1_fresh.content == "Cooking recipes for dinner" + + +def test_update_entry_metadata(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + e = ingest_event(archive, title="T", content="c") + archive.update_entry(e.id, metadata={"key": "value"}) + fresh = archive.get(e.id) + assert fresh.metadata == {"key": "value"} + + +def test_update_entry_missing_raises(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + try: + archive.update_entry("nonexistent", title="X") + assert False, "Expected KeyError" + except KeyError: + pass + + +def test_update_entry_no_change_no_relink(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + e = ingest_event(archive, title="T", content="c", topics=["x"]) + orig_links = list(e.links) + # Update only metadata (no content change) + archive.update_entry(e.id, metadata={"k": "v"}) + fresh = archive.get(e.id) + assert fresh.links == orig_links + + +def test_find_by_hash(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + e = ingest_event(archive, title="Unique", content="Unique content xyz") + found = archive.find_by_hash(e.content_hash) + assert found is not None + assert found.id == e.id + + +def test_find_by_hash_miss(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + found = archive.find_by_hash("nonexistent-hash") + assert found is None + + +def test_find_duplicates(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + e1 = ingest_event(archive, title="Same", content="Duplicate content") + # Manually add a second entry with identical title+content + e2 = ArchiveEntry(title="Same", content="Duplicate content", source="manual") + archive._entries[e2.id] = e2 + archive._save() + + dups = archive.find_duplicates() + assert len(dups) == 1 + assert len(dups[0]) == 2 + dup_ids = {d.id for d in dups[0]} + assert e1.id in dup_ids + assert e2.id in dup_ids + + +def test_find_duplicates_none(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + ingest_event(archive, title="A", content="unique a") + ingest_event(archive, title="B", content="unique b") + dups = archive.find_duplicates() + assert dups == [] + + +def test_add_skip_dups(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + e1 = ingest_event(archive, title="Test", content="Content here") + # Try to add exact same entry with skip_dups=True + e2 = ArchiveEntry(title="Test", content="Content here") + result = archive.add(e2, skip_dups=True) + assert result.id == e1.id # returned existing, not new + assert archive.count == 1 + + +def test_add_skip_dups_allows_different(): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test_archive.json" + archive = MnemosyneArchive(archive_path=path) + e1 = ingest_event(archive, title="A", content="Content A") + e2 = ArchiveEntry(title="B", content="Content B") + result = archive.add(e2, skip_dups=True) + assert result.id == e2.id # new entry added + assert archive.count == 2 + + +def test_entry_roundtrip_with_updated_at(): + e = ArchiveEntry(title="T", content="c", topics=["x"]) + d = e.to_dict() + e2 = ArchiveEntry.from_dict(d) + assert e2.updated_at == e.updated_at + assert "content_hash" in d + + +def test_entry_from_dict_backfills_updated_at(): + """Legacy entries without updated_at should get it from created_at.""" + data = { + "id": "test-id", + "title": "Legacy", + "content": "old entry", + "source": "manual", + "source_ref": None, + "topics": [], + "metadata": {}, + "created_at": "2025-01-01T00:00:00+00:00", + "links": [], + } + e = ArchiveEntry.from_dict(data) + assert e.updated_at == "2025-01-01T00:00:00+00:00" -- 2.43.0 From 238a340251ed3585d49a43e15c003f922e955982 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sat, 11 Apr 2026 19:40:05 -0400 Subject: [PATCH 4/5] feat(mnemosyne): add entry update, content dedup, and find_duplicates Closes #1239 - ArchiveEntry: add updated_at field, content_hash property, touch() - MnemosyneArchive.update_entry(): update title/content/metadata, auto re-link - MnemosyneArchive.find_by_hash(): lookup by SHA-256 of title+content - MnemosyneArchive.find_duplicates(): find groups of identical entries - MnemosyneArchive.add(skip_dups=True): skip duplicate entries on ingest - Backward compat: from_dict backfills updated_at for legacy entries - 18 new tests, all passing --- .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 1016 bytes .../__pycache__/ingest.cpython-311.pyc | Bin 0 -> 2634 bytes .../__pycache__/linker.cpython-311.pyc | Bin 0 -> 4578 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 170 bytes .../test_archive.cpython-311-pytest-9.0.2.pyc | Bin 0 -> 128612 bytes .../__pycache__/test_archive.cpython-311.pyc | Bin 48609 -> 48641 bytes nexus/mnemosyne/tests/test_archive.py | 3 ++- 7 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 nexus/mnemosyne/__pycache__/__init__.cpython-311.pyc create mode 100644 nexus/mnemosyne/__pycache__/ingest.cpython-311.pyc create mode 100644 nexus/mnemosyne/__pycache__/linker.cpython-311.pyc create mode 100644 nexus/mnemosyne/tests/__pycache__/__init__.cpython-311.pyc create mode 100644 nexus/mnemosyne/tests/__pycache__/test_archive.cpython-311-pytest-9.0.2.pyc diff --git a/nexus/mnemosyne/__pycache__/__init__.cpython-311.pyc b/nexus/mnemosyne/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ead8a5023651079392ecbd6862376cea15fc87e8 GIT binary patch literal 1016 zcmZuvOKTKC5boJ`W)m=I^xCMGh0QL4AOsOq3<`+^^)#2JnW@b->BrF1vu1BO`3nT^ zUc`U1$7Q^Tp1dstFF9G=yD<;#Zi?!v?y9f8sxKQGF~N2JTP1$i2>IzR=L_$CMW=62 zej*u3NET%^8L3)Qi@aW!b=623L2t;WY9+0pH)UIOl1|WDa!tiathz}zB8Mbve~yy% ztn-%i*2*j*M8k1e43q|Cim3+rhP literal 0 HcmV?d00001 diff --git a/nexus/mnemosyne/__pycache__/ingest.cpython-311.pyc b/nexus/mnemosyne/__pycache__/ingest.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a253f7b66d3f6eb13ed57e2543eb1f8c9ac70ee5 GIT binary patch literal 2634 zcmaJ@-ER{|5Z}GC&*#rLHdaVN=#`IxOX5^YQKb^0wlq>pNs2(~Lm;wFzD;5{pWVH4 zf}@M%2PzDTM1)!;4}Hi}nkqcxFX-P8Bdb`~7u2c}ycq=~ny1e0*^V7b*XQx<&g|^Y z%x~uWOE?@rFi!p9Wba7`{lzA&A-W9Sy9Ue}($F;0uqNm@Crk^N@uDu~JkuV|dvtG3 znwB{4)qOd6S_Zyf3*Gfh`~8T0v;l2UJ9^hU9niwpQSw;v4jBI0m?Nf@%^OM~TOfMY zAj+TRAC*}`G*i)3OI5Oll~=5JqNp@8pIsoSK;U|@P{>ou1R39L%qg=ppHn7D?y9P* z88WO;bx|P;#IVd^MKv@fry507S73o=iJ1z#VBRIM6I2Z&Z>j7CCQRjT3yf8D;G>fU z$>q(GK|Xg+hP6>etf!l%zi-6n$`_1o3Umbi4v&R zZ;g>HXyK(WLd(L+Q(FK~N0!!HM7NOU0#iMJrb6y8>;j4v7!zt{USar9%=vs#*OYm6 z0g!GfI#EsQQzer(ECAE6k|UPN%i(Okh0qkYaE+tN1kIvhDfwB&Ab@-q=8CFmM052h zOPAwo8g!5&w@f-hR?DF^Q zC8{9^0G@&>37GjJ&5$%Dvq{krQXe^(&=~A8nQ28S@1Gwn(CmV0kx?sG7|pc`ZDcV| zZ;u+}PSHGA9CfQaRVeNI$L9!03-sdBS$8$f3P0Vhywtd!b2G1b;Q&d&W1$eZWr zIVz*w$Rs>lYUTSCdwuCiPtslJqvv|FJv=jQldveLLvRg zkz9|tlXQ?R_W*gpqMDQ(agJDys2U|lZnXwST2S>OF)70x$TTe}Qy<$Xvjs2D9OS6L zHXwlVRaVWA&@ECOHiA0Thas1ZZqNO_`uS%HvDLKy|i_r8XKv_Mk>B~d~j|2(fCGS>x*i9v=$!)t;EpU%||ykPHhR*#JO7H zTqW>YLZP0{2=07R8@u#$?DCVb%hj(Iq5 z-;THw+Yuj!slSo{LzQO z2f@{esytAW2X^Iwx*T5Zy$`~Qw;qg^uh2g5ehMPUUJN{Yj%Zrc4KB;te!SKHZ}83y z!kHO*3?w}E`;oSo_jI1TOuKV{8p|MH9Pix(W(`>#Wo2Vqu%Sm6@Er8F0?wkxcoX`} zcDvW`&eO32Of<*g&I5%eES+g~8dWhN={j-WcW=KhlimZMy1ujlniRMT^aPxf^tt$J zgWAP$+j)8#luYIz$D5+KfD1JN=f-px#H?$Wmw=SfYk*mQJrb`+`|3Rjcq;aKtlrzd zdVKv_BPfNXmk7u!M#>Y72nxj?UU+a}wO9=f)q+F2o*|wws5D*~EKs~WD0@{<_Sc27 zmuFI(qG#ZDDjDfedW~(03t{KLcrq7~_T@Vow7x5 ZUtx6eOg+|LfA3@?9L2{Q=>G`6{lA__e;EJ( literal 0 HcmV?d00001 diff --git a/nexus/mnemosyne/__pycache__/linker.cpython-311.pyc b/nexus/mnemosyne/__pycache__/linker.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be99b2c5f1458ee7978ee97ee88ff4b019ba2d5d GIT binary patch literal 4578 zcmai1TW=f36`o!0@*>kBB~g~-IQGVl<0z6SJGRtBc3dP*noCpHiHj6qA`mO?$XaW; ztIRGfi=Y4l^#j`qsHz%}ilhKjIH}SENFLf>&_6&L0>W+)z(600yfJVe0zdVeS&||x zB^@r$o}HO9mowkF%wG;3>>(&m{>wM>;Qq}&T15#M>A`DYHi zLU|>l2wt(&d^i)9h)iB3hI*SA;ZI~jKEppKnTQd&N=Bo_KfdNz&NZelEtomYGVMi; z+Sg2*rh9rWIr*i6M_tXOdENHV?wWbi(wXV4XmiwCrqtGSmRm4yP)K>qL|@%Dv>c;4 z__CI5hK+m5I6 zQCxgRf4&q92tNK|F#9W*d?nZ1ns|kR59z_1U^WRQ83`(r4Ji{chKymTM9nD1h|zOf z$*9I*BY}6=c+BX-J3^zz5o7SS@(E-=3kCws#Ee5w+2ej+hsxfB#waWPoxid9N0f2H z)b~>!bgQ9US_9 zz^IFpF?@C2a&#}(1`U+JdFWPpkOH$wN>Yhr!3D)2(2sN(2NP=#GAjFuORaepLm!um zYudx9$t7mqz)~l@{L*CJrg_I*vFQZ1zBp;qn+12ba*~6Rvh>o5AI)Y>+w`*8;^93< zkZ!f{g}DboZjt)PL|IwuE#vn96~vcxri%BRA7&IVvvW#O4}cusKq}dv7U8 z*QHkN2e3SRw^j53`BUfs^>>jx8|A<#SGc4lc*gJkzu2Sk3{GZBO%@MQXdPQJ$Sa zKNsCP25FDr*!zJ#Kslemm$Yjw)r=~BEGxz~i_}-eoPMM^sUNbaJsM#gK&+3Geh@zz ztR<_rxj8@14UV~ifAiIX%^eg!0zM%M>*bRD*c|tw+1;%dpWKVyy}gDY?fw?z7HRa7 z)bQrTjf<7H?*6iRdZQT^F`oN*}v-P24 zm8WY%&y?f!wz47eBAyMZQ;MU%STZiZ8@R*(5-{kIN)+J5mZ&IvqfJkgmDadAZwQ zB(nAvB%2|*EhHF2La>!T4|UHk!C+xxO0n=069KXnEaC6G1rZoUM0EDCDXv7#nb)2c zO3#|ccnAKo!a7Gap=5-gIfMCkHg@3M7OaFe=R@9NRS@8Z9SZynt_`emvduSKVKqSMvrbUksX+#48N2%ctt zt4ZQ?ekf3t3&t?+OF__kJpzQxD)!m^`z3c~KkAh*c<{zIFS z8_$>dC=72fj)k zzn45-Ia^Df+DV?OCIR4)t+#&PyB@ngc&tI>gAfN0guW5M8wRYyMz&@A0~d$$F2qh?h$l-q9wa`{NWNoEv> zNs0BPwk3)zgBDv$|BQiX3FJL$-(rOn$Mzm|Z_6bfz`*CoP|K4^U?BFwm zBF?v^F7VnOeuc61Jk4hPo@_Sn7zK;-@oe_}f^IciBH66rex+=#?_Gq=_W@=xW6H*>KTN}Ciymmy4&wPQX8z7ukted=uh$H3YjnPt3P(mF#`at zEFmvq3jY`s*TQuBu9z12P;hKw$K6^$t`cbXBD(@{Zg1`iNFyXk60{&G&;m%DNGGb~ ic;|DUJXY;~>Lgw5ej4Em685;4e9Px^K-SYgRZ2i2{iqc|)pngeeaY?a$e0*kJW=VX!UP0wA4x8Nk gl+v73yCPPgX(0QG`GLd-W=2NF4-7D(h#4pb05c6LEdT%j literal 0 HcmV?d00001 diff --git a/nexus/mnemosyne/tests/__pycache__/test_archive.cpython-311-pytest-9.0.2.pyc b/nexus/mnemosyne/tests/__pycache__/test_archive.cpython-311-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..292960dc62bc85c5b8783f0b5069b65ea758eff3 GIT binary patch literal 128612 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 literal 0 HcmV?d00001 diff --git a/nexus/mnemosyne/tests/__pycache__/test_archive.cpython-311.pyc b/nexus/mnemosyne/tests/__pycache__/test_archive.cpython-311.pyc index 8d83ad0a557f1dae7776369e770876d0c515e525..b5d0cdef4ecac26911410a85dad81e3e766ec062 100644 GIT binary patch delta 253 zcmaF(o2l^+6Yp|fUM>b8*m(O^=Fg41VZD>hdra6Gn7kO87$AE;BQmGf0kMGGm75X1fJ%nArS5{D)w*&WnSY*djsvOZYH#b^T delta 246 zcmZqt!}Rbs6Yp|fUM>b8n0WhE=GBe7VZE$AK;HDp3f)SR<9c`}uk7VzWSYFax0F$T zvTmQu=J38`Mn;y+yZdi2vGFo7FciyAKBy(UdCeq47DnmK_ofvyv+06_zfRt`S$K2S ztX?K2bEe7vv)ng3%w=X~a{|dROlHgw-5j^z4HKImi2o3*HhFO{6I%p`zX{AgzjP}z zWA5bEmB$$qHv6vfWM(S@N!|rZu3yW-%vdvd&1Q+sXExktV(i$wX0tUD= e.created_at def test_touch_updates_timestamp(): -- 2.43.0 From badb1f2b9352a3838a49a39f5dbb044cb3ba4a0b Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sat, 11 Apr 2026 19:40:18 -0400 Subject: [PATCH 5/5] chore: remove pycache from tracking --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9418201..7a4be92 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ mempalace/__pycache__/ # Prevent agents from writing to wrong path (see issue #1145) public/nexus/ test-screenshots/ +__pycache__/ -- 2.43.0