Skip to content

Releases: heidihelena/citevahti

CiteVahti 0.46.0

Choose a tag to compare

@heidihelena heidihelena released this 03 Aug 08:11
ed1b8d1

Added

  • ai_connection.think — an operator-visible chain-of-thought switch for a local
    Ollama model.
    The OpenAI-compatible /v1 shape carries no such switch, so setting
    it routes the rating call over Ollama's native /api/chat (same host, same ceiling
    via options.num_predict); done_reason: length still reads as a truncation and a
    shapeless answer is still a transport failure, never a rating. This is a latency
    control, not a correctness fix
    , and it is a measured trade (2026-07-27, qwen3:14b,
    44-pair prescreen corpus): think: false never truncates and ran ~4.4x faster — and
    lost agreement with the anchor exactly on items whose anchor is unclear (40/44 vs
    35/44; exact McNemar on the 11 discordant pairs p = 0.227, not resolvable at
    n = 44
    ). The default (null) therefore leaves a thinking model thinking; api
    mode rejects the option at connection-resolve time rather than silently ignoring it.
    Locked by tests/test_think_control.py.

Fixed

  • A fresh pip install "citevahti[mcp]" no longer gets an MCP server that cannot
    start.
    The extra pinned mcp>=1.27.2 with no upper bound; mcp 2.0.0 removed
    mcp.server.fastmcp, so a clean install picked it up and citevahti demo / run
    reported "the 'mcp' package is required to serve" — telling the user to install the
    extra they had just installed. The pin is now mcp>=1.27.2,<2, and the serve-time
    error distinguishes a missing package (install the extra) from an incompatible one
    (pin the version), so a future API break says the truth. Found by running the
    fresh-user path in a clean venv. Locked by two contract tests in
    tests/test_mcp_server_transport.py.
  • Product copy says check, not verify. House trust doctrine: CiteVahti records
    claim↔source support, never truth, so public copy says check / test / assess. The
    README's FullVahti section claimed it "writes CiteVahti's verified results back as
    tags"; the same word had spread to the site copy, llms.txt, ROADMAP, INTEGRATION,
    QUICKSTART, a design note, and skills. The site and OG-image generator also still
    labelled the [oo] chip "verified" — that state was renamed to accepted in 0.16.0
    and is frozen in schemas/report.py, so the public site contradicted the shipped
    vocabulary. Left alone on purpose: the hash-chain internals (verify-audit,
    verifyChain/verifyAudit — they check the log, not the evidence), CLI/tool
    identifiers, and doctrine-affirming negations ("not a guarantee").
  • The published pair counts count judged pairs, not rating files. A (claim,
    candidate) pair can carry several claim-support records — one per panel rater by
    design, plus duplicates from ledgers written before opening became idempotent (below).
    Two published surfaces counted the files instead of the pairs they name: the
    evidence-basis sentence read "Of 118 rated claim-candidate pair(s)" on a real ledger
    holding 61 pairs of which 30 were rated, and the PRISMA flow's assessed box reported
    118 against 61 staged — more pairs assessed than staged, an impossible funnel in a
    figure meant for publication, with 88 of those records holding no rating at all. Both
    now collapse to pairs through one shared selector and count only pairs carrying a
    committed human value, matching their own labels. Locked by
    tests/test_methods_counts_pairs.py.
  • A stale candidate record on re-import is reported, and repaired only under audit.
    Dedupe matches on identifier, so re-importing a corrected record found the candidate
    already on file, reported already_in_prior_intake, and stopped — the stale metadata
    stood however many times it was re-imported (a real ledger carried Platt 1964's DOI
    under a different paper's title, with no way out short of txn-undo). Two halves,
    deliberately separate: linking now reports the divergence per field and changes
    nothing — a candidate's title is what a rater read, so refreshing it silently would
    rewrite the record of what was judged — and a new candidate-refresh applies the
    correction explicitly as an audited candidate.correct event carrying every field's
    old and new value. Descriptive fields only; how the paper entered consideration is not
    rewritable by a later import. Locked by tests/test_candidate_metadata_repair.py.
  • Linking candidates hands back the candidates, not a count. claim-link-candidates --json returned {"linked": <int>} while candidate-list --json returned the
    candidate objects — the same concept shaped two ways across two commands, so a loader
    reading .get("candidates") from the link result crashed. The link report now carries
    the candidates themselves, keyed and shaped as in candidate-list, and reports matches
    as well as new links so a re-run describes the same set instead of collapsing to
    linked: 0. Additive: the existing count fields are unchanged. The agent's
    link_candidates gains the same list in compact identity-only form, since its next
    step (start_support_rating) needs a candidate_id. Locked by
    tests/test_link_candidates_returns_objects.py.
  • The de-identified corpus export keeps the rated record, not a blank duplicate. On
    a ledger where pairs carried several claim-support records, _support_rating_for
    picked whichever record sorted last in listing order — on a real ledger it disagreed
    with the shared selector on 24 of 61 pairs and chose a blank started record over the
    rated one on 13, exporting human_support_rating: null where a human had rated. Worse
    than exporting nothing: a dropped rating reads downstream as "no human rated this
    pair", so the corpus understated its own human coverage. The export now uses
    select_support_rating, the one deterministic picker the panel, claim report, and
    agent provenance already share. Locked by tests/test_validation_warehouse.py.
  • Opening a rating twice no longer forks the pair in two. claim-support-start
    minted a fresh rating on every call, so an agent loader that retried a step opened a
    second record for the same pair — a real corpus load left 118 support records for 61
    pairs, and every duplicate was a second, unrated record of a judgement made once.
    Opening is now idempotent: a pair with an open human-unrated rating gets that same
    rating back, and the repeat writes nothing — no record, no audit entry. The panel's
    guarantees hold: a rating holding a human value is never handed out again (reviewer 2
    still gets a record of their own; N is never deflated), and force_new (CLI --new)
    still opens an extra slot for concurrent raters. Blinding: the returned rating may
    already carry an AI value, so claim-support-start --json emits identity fields only.
    Locked by tests/test_support_start_idempotent.py and tests/test_rating_selection.py.
  • import-results now reads the abstract, and says when a record arrived without one.
    The RIS parser handled TY/TI/AU/JO/DO/PY/AN/ER and had no branch for AB (or N2, the
    other common abstract tag); the CSV aliases had no abstract column; BibTeX read no
    abstract field. Everything downstream was ready for it — IntakeHit.abstract,
    ClaimPaperCandidate.abstract, and IntakeService._build_hits passing it through — so
    the field was dropped at the one place it entered, and the command reported status: ok / hits staged: 41 either way. Measured 2026-07-27 on a real 41-record export
    (intelligence-decomposition/sources.ris): 26 records carried an AB abstract and 0
    reached the ledger.
    That is not a cosmetic loss. claims/ai.py::_build_prompt builds
    the blinded rating prompt from the paper's title and abstract and falls back to "(no
    abstract available)" rather than refusing, so on a manual-intake project the AI rater was
    judging support from titles alone, the human panel was shown no abstract either, and the
    resulting ratings — the ones the agreement report and κ are computed from — looked exactly
    like ratings made on the full text of an abstract. Now all three parsers read it (RIS AB
    and N2, including the untagged continuation lines a wrapped abstract spills onto, and
    without doubling an abstract an exporter emits under both tags; CSV abstract /
    Zotero's Abstract Note / EndNote's summary; BibTeX abstract = {...}), and the same
    file now stages 26 of 41 with an abstract. Because coverage is a property of the source
    that the reviewer needs before rating and not an error, the import always reports it: a
    batch where any staged record lacks an abstract carries the warning "26 of 41 staged
    records carry an abstract; 15 have a title only and will be rated from the title alone",
    which import-results prints. Silence that reads as a complete import was the actual
    defect; the parser gap was how it happened. Locked by tests/test_import_results.py.
  • A claim that cites several sources is no longer decided by one accept. Support is a
    property of a (claim, source) pair, but the report read the first accepting decision as
    the whole claim's verdict: the claim turned green, dropped out of the pending queue and
    out of triage, and the papers cited alongside it were left with no human support rating
    and no decision at all. Measured in a real session ledger (2026-07-28,
    intelligence-decomposition): 30 claims, 61 linked candidates, 30 decisions — 31
    candidates never judged
    , while the reviewer's own account was "I accepted all the
    claims, few had several citations that got accepted at the same time". They were not
    accepted together; they were skipped, and the human's blind per-pair rating — the anchor
    everything else is compared against (ADR-0007) — was quietly never collected for half the
    corpus. Nothing invented a value on their behalf (the untouched pairs hold null, and
    DecisionService._derive still refuses to synthesise a support status), but silence that
    reads as completion is the same lo...
Read more

v0.45.0 — the local evidence map, and a panel that heals itself

Choose a tag to compare

@heidihelena heidihelena released this 03 Jul 11:44
ba7b4e0

A feature and a field fix. No engine, safety, or write-path behaviour changed.

Added — the local evidence map (Atlas tab)

The Atlas tab now leads with the real, read-only claim↔evidence graph: nodes are your claims and the papers tested against them; links are coloured and dash-styled by your decision (accept / caution / needs-review / reject / unrated) so the map reads in greyscale too; retracted sources carry a ⊘ ring independent of any rating. An AI view recolours by the AI's second opinion — but only where you have already judged that link; unjudged links stay blinded, never leaking an AI value before yours. Export the graph as a standalone, print-grade SVG figure sized to journal column widths, with legend, N, retraction key, honest-framing note, and an auto-generated caption.

Fixed — the frozen panel (2026-07-02 field incident)

The desktop app's supervisor probed the expensive /api/health (live Zotero/PubMed connection checks) on a 1-second hair-trigger and killed healthy engines under momentary load — leaving the panel window silently driving a dead process: every click and keystroke ignored. Three-layer fix:

  • a cheap read-only /api/ping liveness endpoint (per-process boot_id); the supervisor probes it with a 3 s timeout and a 10-miss threshold;
  • the page heals itself (reconnect.js): connection loss shows a "reconnecting — your claims and ratings are safe on disk" overlay, and a new boot_id (replaced engine ⇒ fresh session) makes the page reload itself — in the app's webview and browser tabs alike;
  • the shell's window upkeep now logs what it does instead of failing silently.

Also fixed: running the developer test suite on a machine with a live CiteVahti.app no longer writes into the user's app logs.

⚠ Updating

  • Claude Desktop extension (.mcpb): Desktop caches the old one — remove the CiteVahti extension, fully quit and reopen Claude Desktop, then install the new .mcpb; confirm with the status tool that it reports 0.45.0.
  • CiteVahti.app: replace your old app with this release's citevahti-0.45.0-macos-arm64.app.zip.

v0.44.3 — CiteVahti.app ships again: signing fix completed (Python.framework excluded from mangling)

Choose a tag to compare

@heidihelena heidihelena released this 02 Jul 12:34
7f003fe

Packaging-only patch; no engine, safety, or write-path behaviour changed. Completes the 0.44.x signing series — this is the release that actually ships the signed CiteVahti.app with the sidecar architecture (and with it the in-app Zotero-connect keyring fix and the panel-assets fix for the app's agent sidecar).

Fixed

  • codesign --verify --strict failed on the CI-built app after 0.44.2's dotted-dir mangling. CI builds with a framework Python, so each sidecar's _internal contains a real Python.framework — a genuine nested bundle codesign signs inside-out on its own. The blanket mangle renamed it, breaking that ordering. The mangle step now excludes *.framework subtrees and only renames non-bundle dotted directories. Verified locally on the CI-identical framework-Python layout: strict verify passes, both signed sidecars serve the panel, and the keychain-stored Zotero key resolves.

⚠ Updating

  • CiteVahti.app: replace your old app with this release's citevahti-0.44.3-macos-arm64.app.zip — earlier apps predate the in-app keyring fix (Zotero connect) entirely.
  • Claude Desktop extension (.mcpb): Desktop caches the old one — remove the CiteVahti extension, fully quit and reopen Claude Desktop, then install the new .mcpb; confirm with the status tool that it reports 0.44.3.

v0.44.2 — CiteVahti.app signable again; its agent sidecar had the blank-panel bug too

Choose a tag to compare

@heidihelena heidihelena released this 02 Jul 12:20
39a1035

ℹ The CiteVahti.app fix described below didn't ship with this release

This release's macos-app CI job still failed (the mangling also renamed the CI Python's real Python.framework, breaking codesign's inside-out bundle signing). The signed, notarized, stapled app first ships with v0.44.3 — get citevahti-0.44.3-macos-arm64.app.zip from there. The three .mcpb files attached here are fine.

Packaging-only patch; no engine, safety, or write-path behaviour changed. Completes v0.44.1: that release's CiteVahti.app never shipped because the CI codesign step failed on the new sidecar bundle layout.

Fixed

  • codesign --deep failed on the sidecar bundle layout, so no signed CiteVahti.app could be built since the shell+sidecars architecture landed. The two --onedir sidecars live under Contents/MacOS/, and codesign treats their dotted directories (*.dist-info, python3.11) as unrecognized nested bundles. build-app.sh now applies PyInstaller's own codesign-safe trick (the shell bundle already ships Frameworks/python3__dot__11): the real directory is renamed with dots mangled to __dot__ and a same-name symlink left behind. Verified on a locally signed bundle: strict verify passes, both sidecars serve the panel, and the keychain-stored Zotero key resolves through the symlinked keyring metadata.
  • The app's citevahti-mcp sidecar had the 0.44.1 blank-panel bug too (its freeze flags were copied from build-binary.sh including the missing --collect-data citevahti). Fixed; the fail-loud freeze guard now checks panel assets in both sidecars.

⚠ Updating

  • Claude Desktop extension (.mcpb): Desktop caches the old one — remove the CiteVahti extension, fully quit and reopen Claude Desktop, then install the new .mcpb; confirm with the status tool that it reports 0.44.2.
  • CiteVahti.app: replace the old app with this release's citevahti-0.44.2-macos-arm64.app.zip — earlier apps predate the in-app keyring fix (Zotero connect) entirely.

v0.44.1 — the standalone .mcpb review panel opened blank

Choose a tag to compare

@heidihelena heidihelena released this 02 Jul 10:42
5afe21a

Packaging-only patch; no engine, safety, or write-path behaviour changed.

Fixed

  • The review panel opened blank from the standalone (Route B) .mcpb. The frozen citevahti-mcp binary collected the citevahti code but not its data files, so the panel's web assets (citevahti/panel/web/) weren't in the executable — every static file 404'd when open_review_panel served the panel from inside the binary. build-binary.sh now passes --collect-data citevahti and fails the build loudly if the panel assets are missing from the freeze manifest. The desktop app (CiteVahti.app, which serves the panel from the citevahti-engine sidecar) and the Python-route .mcpb were unaffected.
  • The CI-built release .mcpb assets had the same gap — plus no keyring. The release workflow freezes the binary with its own PyInstaller invocations, which were missing --collect-data citevahti and never got the 0.44.0 keyring fix — so the win/linux/mac .mcpb files attached to earlier releases shipped with a blank panel and a dead keychain path for "Connect Zotero". All three CI freeze invocations now match the local script, and a platform-neutral "Verify freeze" step fails the workflow if either is missing. The broken .mcpb assets have been removed from the v0.44.0 release.
  • New offline regression test (tests/test_panel_static_assets.py): every asset the panel server promises to serve exists on disk, and every local script/stylesheet index.html references is on the allow-list.

⚠ Updating an installed extension

Claude Desktop caches the old .mcpb — reinstalling over the top does not refresh it. Remove the CiteVahti extension, fully quit and reopen Claude Desktop, then install the new .mcpb and confirm with the status tool that it reports 0.44.1.

v0.44.0 — a local-first review panel non-technical researchers can drive

Choose a tag to compare

@heidihelena heidihelena released this 29 Jun 22:13
ea73716

⚠ The .mcpb extension files originally attached here were broken — removed 2026-07-02

The three citevahti-0.44.0-*.mcpb assets (windows/linux/macos) shipped without the review panel's web assets and without keyring: the panel opened blank and "Connect Zotero" could not read a keychain-stored key. They have been removed and replaced — download the fixed extension from v0.44.1.
If you already installed a 0.44.0 .mcpb: remove the CiteVahti extension in Claude Desktop, fully quit and reopen it, then install the v0.44.1 .mcpb (installing over the top does not refresh Desktop's cache).
The CiteVahti.app bundle and the SBOM below were unaffected and remain.

The CiteVahti panel rebuilt around the people using it in pilots — clinicians and PhD researchers who never open a terminal. No engine, safety, or write-path behaviour changed; this is the front door.

Highlights

  • Navigable surfaces — Review · Manuscripts · Checks · Atlas · Output · Settings, with a persistent claims queue, so a task is always one obvious click away.
  • "Choose a file…" manuscript intake — start by picking your Word/Markdown file, dragging it on, or pasting the text. No command line. A plain "On your Mac · nothing uploaded" banner states the privacy posture up front.
  • "Show in Finder" on every written file — exports and saved records name the file and reveal it in your OS file manager (constrained to the project folder, loopback + CSRF-gated, reveal-only).
  • Review-record timeline — the hash-chained audit log surfaced as a readable, timestamped list you can export for a supervisor, co-author, journal, or registry.
  • A minimal design system and a full accessibility pass (named focus-trapping dialogs, a live error region, a keyboard-reachable review-record badge).
  • "Run unit tests" → "Check claims" — the primary action now speaks the researcher's language.
  • A 28-test frontend behaviour suite driving the real shipped panel (node:test + jsdom, plus Playwright for the full flow and interactive a11y).

Install

Most people: download the Claude Desktop extension for your platform below and double-click it — no terminal. See the README for the one-click steps and other install options.

Full notes in CHANGELOG.md.

v0.43.0 — supply-chain hardening & whole-package typing

Choose a tag to compare

@heidihelena heidihelena released this 28 Jun 19:39
7d6b6fb

Supply-chain & type-safety hardening, plus security and bug fixes.

Security & supply chain

  • CI scanners: ruff (E/F/S) + mypy gating, CodeQL, pip-audit, OSV-Scanner, OpenSSF Scorecard
  • CycloneDX SBOM attached to each release; committed uv.lock; enforce_admins branch protection
  • Fixes: defusedxml for PubMed XML (S314); TSA-URL scheme guard (S310)

Bug fixes

  • Library dedupe no longer misses DOIs in non-canonical form (prefix/case)
  • corpus_diff degrades gracefully instead of crashing on a missing to_snapshot_id

Types & safety

  • Whole-package mypy — zero ignore_errors backlog (162/162 files)
  • Dual-rating flag/score regression tests (blinding window, adjudication rules)

Full notes: CHANGELOG.md (0.43.0).

v0.42.0 — source reuse rights (license-scan)

Choose a tag to compare

@heidihelena heidihelena released this 27 Jun 21:38
b8573d0

Each candidate can now carry its reuse rights — oa_status (gold/green/hybrid/bronze/closed) and license (e.g. cc-by) — filled from OpenAlex (already called for retractions) by DOI/PMID. Surfaces: 'citevahti license-scan --json', a panel '⚖ Scan licences' button, a neutral chip on the candidate card, and the two fields in the candidate API.

Reports, never decides: it records what the source's licence IS so a human (or a downstream content hub) can judge reuse — CiteVahti never says a source is OK to republish. Unknown/offline leaves the fields unset. No new egress (OpenAlex already disclosed).

🤖 Generated with Claude Code

v0.41.0 — tufup auto-updater scaffold

Choose a tag to compare

@heidihelena heidihelena released this 27 Jun 13:59
956f267

Signed auto-updates for the frozen desktop app, built on tufup → The Update Framework. Updates are signed metadata + hashes, so a client accepts a new version only if signed by CiteVahti's offline keys — integrity even if the update server is compromised. The maintainer flow keeps the root/targets trust-anchor keys offline (the TUF key split).

INERT and safe until configured: with no update URL + no bundled trusted root (the state until the founder generates keys), every entry point is a no-op — never touches the network, never affects a launch, and nothing is auto-applied silently. docs/AUTO_UPDATE.md has the security model, key generation, per-release sign flow, and a key-management runbook.

🤖 Generated with Claude Code

v0.40.0 — claim-verify (offline claim-vs-text)

Choose a tag to compare

@heidihelena heidihelena released this 27 Jun 06:02
684b593

citevahti claim-verify --claim "…" --text-file src.txt --json checks a claim against PROVIDED text, fully offline (no Zotero, no ledger) — the integration seam for an external citation reviewer that already has the cited source's text. Returns coverage, status (terms_present/terms_missing), present/missing terms, and a contradiction + inspectable polarity_cue. Never a verdict. Text from --text / --text-file / stdin. Completes the verifier surface alongside 0.39.0's claim-check --json; both documented in docs/INTEGRATION.md.

🤖 Generated with Claude Code