agentleFS
oldantiqueoldantique/zotkit/CLAUDE.md

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
  1. Distinctive:

    Never name other sessions' confidential projects in public surfaces (commit messages, release notes, issues) — "a downstream session" is …

    line 20
  2. Distinctive:

    Update README "Limits & roadmap" / changelog notes if behavior changed.

    line 26
  3. Distinctive:

    Commit, push to main.

    line 27
  4. Very specific:

    Create the GitHub release via REST — gh release create has given a spurious scope error here, the API works: gh api …

    line 28
  5. Very specific:

    Tests stay offline: no network, no .env, no credentials, no live library.

    line 77
  6. Very specific:

    Windows text IO: every open() for text must pass encoding="utf-8".

    line 91
  7. Very specific:

    Credentials: never print, cat, echo, or commit a .env or API key/WebDAV password, including a developer's local one.

    line 100
  8. Distinctive:

    If you change the upload/download code, keep this doc in sync (it is itself a headline feature of the repo).

    line 106

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

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

  1. Bump the version in both pyproject.toml and zotkit/__init__.py (keep them equal).
  2. Update README "Limits & roadmap" / changelog notes if behavior changed.
  3. Commit, push to main.
  4. Create the GitHub release via REST — gh release create has 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)
  5. .github/workflows/publish.yml then publishes to PyPI automatically via Trusted Publishing (no tokens anywhere). Tags are plain vX.Y.Z.
  6. Verify: pipx run --spec zotkit==X.Y.Z --no-cache --pip-args="--no-cache-dir" zotkit --version once 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.
  7. 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 predicate
    • fetch_arxiv_batch against a canned feed + the CrossRef → DataCite fallback and its type/field mapping), test_enrich.py (plan_enrich VoR gate, the --doi same-paper gate, the "source has no abstract" note + its status invariant, set_abstract guard/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 vs SCHEMA_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 / --verify on both storage branches against faked HTTP), test_fetch_warning.py (git-worktree detection on tmp repos), and test_lint.py (lint_tags accepts both str and Zotero-native {"tag": ...} shapes). Run with pip 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-only zotkit audit against the real library, plus a create/abstract round-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.yml runs 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 pass encoding="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 .env or 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's status values feed the --all/--missing summary 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's format=versions map on every call (ADR-0004). Anything that needs every top-level item/attachment/collection goes through Zot.listing(kind), never z.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 shipped zotkit/data/journal_abbreviations.tsv, or a user's ZOTKIT_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's format='bibtex', which reparses/reflows entries through bibtexparser.
  • Tag conventions are data + code, not prose: users supply conventions.toml (see conventions.example.toml); zotkit.core.lint_tags enforces it in create/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).

Download now (ZIP)

  • In this repository2 other files it gives its agents.

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.