zotkit
CLAUDE.md26 starsChanged 3 months agoMITView on GitHub
This file is for AI-assisted development sessions on zotkit itself. If you are an agent helping a user use zotkit on their library, read AGENTS.md instead. This repo is the Python package only (headless Zotero Web-API + WebDAV CLI/library). The Zotero Reader plugin that once lived here moved out (ADR-0003) and is no longer maintained or linked (ADR-0006) — out of scope for this repo. Data conventions (version of record, abstract-source stamp) are documented in README — keep docs and…
- Reads credentials
- Installs packages
- Commits and pushes
We split this CLAUDE.md into the rules it gives an agent
8 rules. About 0 are common, 4 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 20
Never name other sessions' confidential projects in public surfaces (commit messages, release notes, issues) — "a downstream session" is …
- Distinctive: line 26
Update README "Limits & roadmap" / changelog notes if behavior changed.
- Distinctive: line 27
Commit, push to main.
- Very specific: line 28
Create the GitHub release via REST — gh release create has given a spurious scope error here, the API works: gh api …
- Very specific: line 77
Tests stay offline: no network, no .env, no credentials, no live library.
- Very specific: line 91
Windows text IO: every open() for text must pass encoding="utf-8".
- Very specific: line 100
Credentials: never print, cat, echo, or commit a .env or API key/WebDAV password, including a developer's local one.
- Distinctive: line 106
If you change the upload/download code, keep this doc in sync (it is itself a headline feature of the repo).
≈ “Common” is an estimate: at least 1 in 5 of the 25,680 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Development context (for agent sessions in this repo)
This file is for AI-assisted development sessions on zotkit itself. If you are an agent helping a user use zotkit on their library, read AGENTS.md instead.
Scope
This repo is the Python package only (headless Zotero Web-API + WebDAV CLI/library). The Zotero Reader plugin that once lived here moved out (ADR-0003) and is no longer maintained or linked (ADR-0006) — out of scope for this repo. Data conventions (version of record, abstract-source stamp) are documented in README — keep docs and code consistent with it.
Cross-session routing
- Crash/bug reports arriving directly from any session may be fixed and shipped right away when the fix is behavior-preserving and tested — but notify the HQ session (private zotero repo) in the same breath. Feature requests, CLI surface changes, and anything touching conventions/policy need an HQ work order first. Never name other sessions' confidential projects in public surfaces (commit messages, release notes, issues) — "a downstream session" is enough.
Releasing
- Bump the version in both
pyproject.tomlandzotkit/__init__.py(keep them equal). - Update README "Limits & roadmap" / changelog notes if behavior changed.
- Commit, push to
main. - Create the GitHub release via REST —
gh release createhas given a spurious scope error here, the API works:gh api repos/oldantique/zotkit/releases -X POST -f tag_name=vX.Y.Z -f name=vX.Y.Z -F body=@notes.md(draft the body in a file and leak-check it first; a release body is public) .github/workflows/publish.ymlthen publishes to PyPI automatically via Trusted Publishing (no tokens anywhere). Tags are plainvX.Y.Z.- Verify:
pipx run --spec zotkit==X.Y.Z --no-cache --pip-args="--no-cache-dir" zotkit --versiononce the publish run succeeds. Without--no-cache-dir, pip can reuse a stale index and say "no matching distribution" for a version PyPI already serves. If the publish fails (v0.9.0: Windows-only test), don't move the tag. Fix, release the next patch version, and mark the failed release's body as not on PyPI. - Close the GitHub issues the release shipped, with a comment naming the version.
SCHEMA_BASELINE (zotkit/core.py) is the Zotero global-schema version the
writes were last verified against; doctor warns past it. When it warns, verify
create / enrich / attach on a temp item against the new schema, then bump it in
a release.
Tests
tests/is the offline pytest suite, filed by module, never by release (a file named after a version guarantees an archaeology layer per release):test_metadata.py(abstract cleaner + two-layer version-of-record predicatefetch_arxiv_batchagainst a canned feed + the CrossRef → DataCite fallback and its type/field mapping),test_enrich.py(plan_enrichVoR gate, the--doisame-paper gate, the "source has no abstract" note + its status invariant,set_abstractguard/force/stamp matrix),test_audit.py(bucketing, non-paper types, type-appropriate identifiers, supplementary-PDF titles from real libraries),test_find.py(--any/--abstract matching, hit annotations, snippet shape),test_lookup.py(find --id: id parsing, every place an item carries an identifier, one line per input),test_journals.py(ISO 4 normaliser: source order, LTWA word rules on a fixture — never the real LTWA, candidates vs registered forms),test_cite.py(short-citation format, issue year),test_cache.py(version-map reconciliation: add/change/delete, chunking, corrupt-file and disabled fallbacks),test_dedup.py(dedup_maps/duplicate_key + skipped-meta keys),test_show.py(one-line format, attachment lines, sanitized errors, --verbose),test_doctor.py(schema version vsSCHEMA_BASELINE: ok / warn / unreachable),test_export.py(_rekey + provenance header + faked HTTP),test_fetch.py(attachment-key resolution, why-nothing-saved errors + exit 1),test_oa_pdf.py(create --oa-pdf: Unpaywall lookup shapes, the stop rule — one attempt, no retry on 429, a bot wall's HTML is a refusal, a refusal ends that host's downloads for the run while other hosts go on),test_attach.py(show's stored/missing/linked check without a download,attach --title/--verifyon both storage branches against faked HTTP),test_fetch_warning.py(git-worktree detection on tmp repos), andtest_lint.py(lint_tags accepts both str and Zotero-native {"tag": ...} shapes). Run withpip install -e ".[dev]" && pytest. pytest is a dev extra only — runtime deps stay exactly pyzotero + httpx.
- Tests stay offline: no network, no
.env, no credentials, no live library. Anything that needs those is a manual smoke step: read-onlyzotkit auditagainst the real library, plus a create/abstractround-trip on a temp item that is deleted (and confirmed 404) afterwards. - CI (
.github/workflows/ci.yml) runs the suite on every push/PR across ubuntu + windows × Python 3.11/3.13 — Windows is not optional (the v0.1.x release-breaker was a Windows UTF-8 bug).publish.ymlruns the same test job before build, so release step 3 (push) now implies CI must be green; a failing suite blocks the PyPI publish. - Still-open debt: the Zotero Storage attachment branch has no automated or live test — manual verification only (see Hard constraints).
Hard constraints
- Windows text IO: every
open()for text must passencoding="utf-8". A v0.1.x release broke on Windows exactly this way. - Never claim zotkit is the "only" WebDAV-write implementation — zotero-mcp
shipped the same K.zip/K.prop upload in its v0.4–v0.5. Honest differentiators:
fully headless for every feature, purpose-built CLI, conventions-in-code,
doctor, the published format doc. - Attachment storage has two branches: the WebDAV branch is regression-tested live; the Zotero Storage branch goes through pyzotero and has NOT been tested against a real storage library — flag any change there for manual verification.
- Credentials: never print, cat, echo, or commit a
.envor API key/WebDAV password, including a developer's local one.
Where things are
docs/webdav-format.md— the reverse-engineered WebDAV attachment format. If you change the upload/download code, keep this doc in sync (it is itself a headline feature of the repo).docs/adr/— architecture decisions; add a numbered ADR for structural changes.zotkit/metadata.py— identifier → item-JSON fetchers (arXiv/CrossRef). ALL request pacing lives in its_get(per-host intervals) — never add sleeps or retry logic in callers.zotkit/enrich.py— in-place completion of existing items. Core invariant: the item key never changes; merge policy (fill-empty-only, creator prefix rule, append-only Extra, abstract-source stamp) is documented in its module docstring and README → "Enriching existing items". A second invariant, easy to break:plan_enrich'sstatusvalues feed the--all/--missingsummary counts and everything downstream reads them, so a reporting-only finding (e.g. "the source has no abstract either") gets a note — never a new status, bucket, or exit code.zotkit/cache.py— local cache of whole-library listings, reconciled against the server'sformat=versionsmap on every call (ADR-0004). Anything that needs every top-level item/attachment/collection goes throughZot.listing(kind), neverz.everything(z.top())directly. Invariant: a cached answer must be as complete as a fresh one (find's zero hits are trusted), so no TTL and no serving stale data on error.zotkit/journals.py— the one ISO 4 journal-abbreviation normaliser (ADR-0005), used by create/enrich/audit/cite. Invariant: only a registered form (the shippedzotkit/data/journal_abbreviations.tsv, or a user'sZOTKIT_JOURNALS) may replace a stored value or count as verified; LTWA-rule output is a candidate that only fills empty fields. The LTWA itself is never committed or shipped (ISSN database rights) — tests use a hand-written fixture, and the table holds registered forms only, never rule output.zotkit/export.py— BibTeX export. Cite keys are ALWAYS rewritten to the Zotero item key (that's the command's purpose); entries are fetched raw over the Web API on purpose — do not "simplify" to pyzotero'sformat='bibtex', which reparses/reflows entries through bibtexparser.- Tag conventions are data + code, not prose: users supply
conventions.toml(seeconventions.example.toml);zotkit.core.lint_tagsenforces it increate/tag. Don't add convention rules that exist only in documentation. skills/zotkit/SKILL.md— the end-user agent skill; update it when CLI commands/flags change (it lists exact invocations).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

