From 5e26ee0a7d2eafa7c4bfc247fe06aa688a5dfd65 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sat, 11 Apr 2026 19:38:17 -0400 Subject: [PATCH] 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 z#~haru#f8z=FK`lW&mtMS&B31!V1TbHAu!V%J!sX*JDM!1_f=j%GyHu`r|$<;eth5 zr_nWDWbTTH=;|MnU>jcm&1on(=Dal}LkmgEkSfm*^#|j51i=U2xeqH52Qq9T0<|zR zs>vw|5CdF#Rpl@dJDCSE2;_%k!4j#kcV|oEun4e9=6~IIl~!QIazKPwDJiz~!_Po` z8p{v)r&M+xFICVB`%!|@^2gK&wU6;&X7KVSGe_J>uHcK?#W!Y&o2RJLa!x6f@vd9y zR&CG%?a2hO_T|B!B?Fc|WF0(}J7 ze4$uXvH4<%&JGixOyoTTn8LsghhDz8?Hyb#WLf3p3<;No7l?eOz5Ei!jG?O} z6&tZ5a0JO@nQPvaMCF!b<(7EmmPe_&1}etGCt5A{VPTG4rthdT9H#k!%|ovt@i_C4 z9#;WZmyr%S>jMi{Gc-LuDg=&$Mg!vc&Xx1PsxvpZrAB56YOH~UH5|#bR9$3QY|9NV zed=&;$JQM?wa7&o7tEq`XjFy?DvKQrJ4;z)Lp{49*;QJ)88;&46D^{gMHc-~ov|*y zi;7CEf)k|gU}@GN_1d&vTr1G!$mAlkMDyRJE=O5`cqu~6%iIMcjSk5B0VpziC1TFS zURx?fDE}^)?M0mQhe$>4tq)7p(Cal!p3H!D4YAd{o%zD&-!Y=(R{dA}rf8DGf_`2r z%yHz~9n5Xx0YwT6y>t7O{+JBV3UezY_?Arfa$NH;hmy;hW#)fleJU?gC6i4jD#BJ%_MkS-J2 zPO`K~TX~H^H+)chVt`8 ze<`M;q9FdEDc1X;^{(}rPC;zVK_?W*DVSOBj%yOhkdD%8ZU(gpGCq8@a2J?q-S^;aPSz zFFBiIU9ocsr#I>JE<3j@IkzlUC7e$solnKto?>4wwr9Cz$5P9VM9WjjmZ$JJ6QxIz zrANSoVdi1By6fZB>#-&+Y<1N&X)D6T?9+Z-sxBbGR-D!Qm8$}k5ZZ*6E)N;LU!x_& zvys7>^@K*BHh?GihWpV9e2Y=<$=mb@1{0a%hHV(;M_Kl0u1M#X@q{_*d*!SN%f%uN z65g}rhK(&UUU*>$S`b$)YhjTJ%)2al6b!)+m^NmSYM$#tdpW`AY4Xvw%VV;0gg(u_ z7tx?EWOxC#vI=vv;K3RLgL2L~nEQzu#%iM(WgEYl0xQb?4!(#4O~?K`A{g$!Af{?{w$b!rs!tW$*9BSKw@oKikFJ$3=!6$mTI0$XC*S6B2@I2|np&sX57cXx{?@J9HU zaQ74{tzU*&r7~`{Yp<%$mw8T`>&ev1^FBE)an+9}CKU9%6$PxeyNjj1bEPWJl8B z+FriL3K)uUj260-A#0m-{sECqXy4uXF$uoA-UH?Z^vt?w0nyu6b&Z>B^ zm}(H`k0u+su%KFNN4Rh9XwumPDnFmFGZ0}10949p_3+U@Up<@;!c~TgDIc=_pvBt& z@MNYr(WtTS0VHaBleN9!Bjh?+yj-(+sb=${WpO4^voBe*50)uTPjq*}*&03?KKih@ zl8W2fr4%xU_^*rG>)4mb1WzUjh)mYjUgPP5P=Znik*x%J2s}-IEY*1G8A{PrSavZE zj%;0JcJ?_sYmkr8kCjK=XBV0K%n9{23RDT2f^L&9QrcedUe-YZx~Jqm;2&h2eeVsr zo|Cm%rpb;OXYq2e7v`0g>II+a{!CG0^bphden=@+qmS4;YO3_PgZkE~SIIg~%wZPi6ePDosv=0oViv|Y9C#C5zN;?Mz zE+V+Fa>X_{;VmB|&`e;8z;y!e5tt{yqz6Bv)b9hN?T}+l`Q_kM zzQ{p>F8mpRzaYSj?`%{!3a=(pAToJ7fn5ZiA<#?r9itQ}Z@J$bN%pvPWD%tZRNz1G zo{@;9%-@R9Y_Y?08Ng|=RKXr7ACx%goC>O~7B4)OGQeF)R||l4gJlzNg$`JYES>PP z$_F(a7Dx*UfZsAyT41$Q0JNJdgK(?PgGDY&B}{L1pji-GQ|mXb*w$GPW2*q@H&`wG z$Sn`L1#8KQ(O`jTSO$Dy!nwCz=4Xq94VzXhyDhLnC;%=Q8t8?3&||kidZGuF`wSL1 zoUZ~88=EaKi7NmO8_pUmFoVd0ZyKxVyXe7|3Oc6;8*AvC9&}T4HLXjvYye7Lac!eJ z3V`zlJGHVNR66OL9yByr>Q{ts0vu^mC-s)dFQF6Dm>ZhLg60@=G7+`12#~f-`GO;3 zqvvEZ9a)1{X+;H@$mnzx7Au_&bdFEJHzja&!r#e=Du&L})w$O={liv`mKX6f8y`6X z=WBZpAN70XvnUaEA@FkmtOj5(VBi?c7&rh7e7DPenDzdtOmx>mYky;x*GLl%Z#u=BrJ 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.