## 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`