Skip to content

## Implement: biblio zotero push-tags — write enrichments back to Zotero

Phase

Goal

(promoted from note)

Context

(see source note)

Prompt

Fix the issue described in /storage2/arash/projects/projio/docs/log/issue/issue-arash-20260403-210020-146442.md. Read it first, then implement the proposed fix.

Acceptance Criteria

  • [ ]

Result

Completed in 243.3s (1 steps, model: opus)

Session: 21f69514-a88f-4fb6-bf8d-116d1bdfe3e5 Result note: /storage2/arash/worklog/workflow/captures/20260403-211716-29f634/note.md

Output

The CLI parsing works correctly — it reaches the Zotero config check, which is the expected error since there's no Zotero config in this project. The implementation is complete across all four layers:

1. **`packages/biblio/src/biblio/zotero.py`** — `ZoteroPushResult`, push helper methods on `ZoteroClient` (`update_item`, `create_note`), enrichment collectors (`_collect_enrichment_tags`, `_collect_extra_fields`, `_load_summary_html`), conflict detection, and the `push()` orchestrator
2. **`packages/biblio/src/biblio/cli.py`** — `biblio zotero push` subcommand with `--citekeys`, `--tags/--no-tags`, `--notes`, `--ids/--no-ids`, `--force`, `--dry-run`
3. **`packages/biblio/src/biblio/mcp.py`** — `zotero_push()` MCP function
4. **`src/projio/mcp/biblio.py`** + **`src/projio/mcp/server.py`** — `biblio_zotero_push` MCP tool

Key design decisions matching the spec:
- **Tag namespacing**: All pushed tags use `biblio:` prefix (`biblio:status/reading`, `biblio:topic/...`, `biblio:concept/...`, `biblio:tag/...`)
- **Optimistic concurrency**: Compares stored vs current Zotero item version; skips conflicts unless `--force`
- **Union merge for tags**: Only adds new tags, never removes existing ones
- **DOI write-if-empty**: Only writes DOI if Zotero's DOI field is blank
- **OpenAlex ID in extra field**: Appends line if not already present
- **Summary notes**: Created as child notes with `[biblio summary]` marker; skipped if already exists
- **Logging**: Operations logged to `.projio/biblio/logs/runs/zotero_sync.jsonl`

## Next Steps

1. Add unit tests for `push()` by mocking `ZoteroClient` methods, similar to how pull tests might be structured — test tag collection, conflict detection, and dry-run behavior.
2. Update the CLAUDE.md `mcp/biblio.py` tool listing to include `biblio_zotero_push` alongside the existing `biblio_zotero_pull` and `biblio_zotero_status`.
3. Implement bidirectional tag sync (Phase 3) — detect tag changes made in Zotero and reflect them back in `library.yml`