fix: --force re-render progression, cache dir loading, and Joplin link preservation. Co-Authored-By: Fable 5 <noreply@anthropic.com>

This commit is contained in:
JesseMarkowitz
2026-06-12 21:25:47 -04:00
parent 9e1a8ab7cb
commit 456975ad50
6 changed files with 159 additions and 18 deletions
+36 -2
View File
@@ -84,7 +84,13 @@ class Cache:
if provider not in self._data:
self._data[provider] = {}
self._data[provider][conv_id] = {
# Preserve the Joplin link across re-exports. Without this, re-rendering
# a conversation (e.g. a forced re-export to pick up new formatting)
# would drop joplin_note_id, and the next sync would create a duplicate
# note instead of updating the existing one. exported_at is refreshed,
# so get_joplin_pending() still flags the note for an update.
prev = self._data[provider].get(conv_id) or {}
entry = {
"title": metadata.get("title", ""),
"project": metadata.get("project"),
"created_at": metadata.get("created_at", ""),
@@ -92,20 +98,48 @@ class Cache:
"exported_at": datetime.now(tz=timezone.utc).isoformat(),
"file_path": metadata.get("file_path", ""),
}
for carried in ("joplin_note_id", "joplin_synced_at", "joplin_resources"):
if carried in prev:
entry[carried] = prev[carried]
self._data[provider][conv_id] = entry
self._data["last_run"] = datetime.now(tz=timezone.utc).isoformat()
self._save()
def get_new_or_updated(self, provider: str, conversations: list[dict]) -> list[dict]:
def get_new_or_updated(
self, provider: str, conversations: list[dict], force: bool = False
) -> list[dict]:
"""Filter a conversation list to only new or updated conversations.
Args:
provider: "chatgpt" or "claude"
conversations: List of raw conversation dicts from the provider.
Each must have an ``id``/``uuid`` and ``updated_at``/``update_time``.
force: When True, return every conversation regardless of cache
state — used by ``export --force`` to re-render everything
without clearing the manifest (which would drop Joplin links).
Returns:
Subset that needs to be exported.
"""
if force:
# Re-export everything, but order by least-recently-exported first
# (never-exported sorts first). With a per-run cap, the batch just
# re-rendered gets a fresh exported_at and sinks to the back, so
# each subsequent run advances through the archive instead of
# repeating the same head. Conversations without an id are dropped.
entries = self._data.get(provider, {})
def _exported_at(conv: dict) -> str:
conv_id = conv.get("id") or conv.get("uuid", "")
entry = entries.get(conv_id) or {}
return entry.get("exported_at") or ""
return sorted(
(c for c in conversations if c.get("id") or c.get("uuid")),
key=_exported_at,
)
result = []
for conv in conversations:
conv_id = conv.get("id") or conv.get("uuid", "")