Skip to content

Releases: xiaolai/klode

v0.5.0 — breaking changes, correctly numbered

Choose a tag to compare

@xiaolai xiaolai released this 15 Aug 02:16

This is the same code as v0.4.3, under a number that tells the truth about it

0.4.3 shipped these breaking changes as a patch, which a klode>=0.4,<0.5 pin picks up
automatically. That was the wrong number for them. 0.4.3 is yanked on PyPI — yanked, not
deleted, so an exact pin still resolves and nobody already depending on it is broken.

git diff v0.4.3 v0.5.0 over the package is one line: the version string.

⚠️ Breaking changes

Five kinds of input that worked on 0.4.2 now raise. Every one of them was a guard failing open.

What now raises Was Why
permutations of 3, 5, 7… (constructor and [judge].permutations) accepted an odd count presents one level order more often, keeping the position bias the average exists to cancel — at higher cost than the even number below it. Use 1 or an even number to 16.
a non-integer hurdle (59.6, True, "60") via the API or the review service accepted, truncated truncation only moves the bar DOWN — int(59.6) is 59, so a draft scoring 59.52 passed a bar it did not clear
a shelf or extra_guard_dirs entry beginning with - accepted it reaches git ls-files as a pathspec, where a leading dash reads as a flag and silently disables the copyright-leak guard
an absolute --pattern to klode normalize accepted, scanned nothing glob ignores root_dir for an absolute pattern, re-arming every metacharacter in the library path
klode build when enumeration disagrees with the disk rewrote the board it overwrote INDEX.md with an empty board and reported success

Verdicts move. The gate compared a rounded score against the hurdle, so any draft within half
a point below it passed. Go becomes Recycle at the boundary — one direction only, never the
reverse. Rubrics whose criteria share a single scale cannot produce a fractional mean and are
unaffected.

Stored calibrations stop claiming coverage. Calibration now pins the judge (model,
permutation policy, prompt version) as well as the rubric, and reports calibrated=False until
re-measured.


Thirty-five defects fixed across three passes: an adversarial review of the plan before it was
written, then two independent file-by-file re-audits of the work itself. The re-audits found
22 more, ten of them inside guards added earlier in the same effort — that pattern is
documented in the changelog rather than smoothed over.

Highlights: a public address spelled as an integer walked past the plaintext-transport guard
(http://134744072 resolves to 8.8.8.8); a glob metacharacter in a library path made the
fail-closed linter exit 0 over a rotted citation; an edited rubric could not be re-approved by the
command its own error message named; normalize --check certified a corpus it had read half of;
and check.py's in-tree backup sweep had never matched anything in its life.

This line is also the first klode release whose test suite ran before publication — every tag
before v0.4.3 published without it, because tests.yml triggers on branch pushes and a tag is not
a branch.

981 tests across Python 3.11–3.14. Full detail in CHANGELOG.md.

v0.4.3 — SUPERSEDED by v0.5.0 (yanked on PyPI)

Choose a tag to compare

@xiaolai xiaolai released this 14 Aug 06:53

⚠️ Superseded — do not use this version

Use v0.5.0 instead. It is the
identical code (git diff v0.4.3 v0.5.0 over the package is one line: the version string).

This release carried breaking changes but was numbered as a patch, so a klode>=0.4,<0.5
pin would pick them up without anyone choosing to. It is yanked on PyPI — yanked, not
deleted, so an exact klode==0.4.3 pin still resolves and nothing already depending on it
breaks.

Nothing was wrong with the code. The version number was wrong about the code.

See the v0.5.0 release notes for the full
list of breaking changes and the 35 defects fixed.

klode 0.4.2

Choose a tag to compare

@xiaolai xiaolai released this 12 Aug 18:52

Documentation only — no behaviour change. Cut as its own release because it fixes a
discoverability defect in the previous one.

Why this exists

v0.4.1 shipped a breaking change to exit codes and documented it in the 0.4.0 section — and
0.4.0 was never tagged. So anyone upgrading from 0.3.0 read the 0.4.1 notes and never met the
change most likely to turn their CI red. A breaking change nobody can find is an undocumented
breaking change.

That warning now opens the 0.4.1 section in CHANGELOG.md. The v0.4.1 tag is
deliberately left where it is: rewriting a published tag is worse than the inconsistency it would
fix, so a later release carrying the note is the honest correction.

Still the thing to know, if you are coming from 0.3.0

Commands that could not do their work exit 2 instead of 0 — check, build,
normalize --check, zoom --level content, and the bake-off. If your CI runs klode check on a
machine without the corpus installed, it will now fail.

That is deliberate: klode check --strict used to print OK: 0 errors and exit 0 on a library
whose citation-rot check never ran, and a fresh clone has no corpus because the corpus is
git-ignored. Every anchor could have rotted while the build stayed green.

1 = measured and failed · 2 = could not measure · 0 = the work happened. Pass
--allow-unmeasured to accept a gap knowingly. Full table in the
0.4.0 notes.

Also in this release

Remote layout backends ([ingest].docling_url, marker_url) are now documented as the
recommended path rather than a fallback for machines without a GPU. It is the configuration
with the better failure behaviour: a remote conversion has a deadline that scales with the
document, while the in-process backends have no wall-clock bound at all. Choosing the bounded
path avoids that gap instead of papering over it, and keeps the client dependency-free — which is
this project's headline claim.

Verified end to end on a machine with neither kreuzberg nor docling installed.

klode 0.4.1

Choose a tag to compare

@xiaolai xiaolai released this 12 Aug 16:44

⚠️ Upgrading from 0.3.0? Start here.

0.4.0 was prepared but never published, so v0.4.1 carries everything since v0.3.0.

The change most likely to break you

Commands that could not do their work now exit 2 instead of 0.

command condition was now
check corpus absent, or any card's source not installed OK, 0 ABSTAINED, 2
check --entail the entailment backend could not load OK, 0 ABSTAINED, 2
build nothing to build 0 2
normalize --check no file matched, or files were unreadable 0 2
zoom --level content the source is not installed 0 2
eval/extract_bakeoff.py nothing could be ranked 0 2

If your CI runs klode check on a machine without the corpus installed, it will now fail.
That is deliberate. klode check --strict used to print OK: 0 errors and exit 0 on a library
whose citation-rot check never ran — and a fresh clone has no corpus, because the corpus is
git-ignored. Every anchor could have rotted while the build stayed green. CI reads exit codes, not
notes.

The convention is now uniform: 1 = measured and failed · 2 = could not measure · 0 = the work
happened.
Pass --allow-unmeasured to check, build, or normalize to accept a gap
knowingly — the difference between an accepted limitation and a silent one.

Also breaking

  • A card's file: is confined to <lib>/<shelf>/<name>.txt. It was confined only to the
    library root, so a card could point at library/.env or library.toml and zoom --grep would
    print the matching lines. Cards travel through the registry, so that field is untrusted input.
  • Settings-backed CLI flags default to None rather than to their value; --max requires
    --grep; normalize --apply and --check are mutually exclusive; --json on a command that
    cannot emit it now refuses instead of printing prose.

What's new

  • Extraction integrity — an ingest can say whether the text it wrote represents the document,
    and refuses the shelf when it does not. Containment, inflation and per-page reading order, plus
    rendered-page ground truth (pdftoppm + tesseract) — the only signal not downstream of another
    extractor. Four states, and abstained is never verified.
  • ~/.klode/settings.toml — argument → environment → file → default, with the winning source
    recorded. klode settings --explain describes every key; --lint validates the file including
    shadowed values. Credentials can never be settings, and a test enforces it.
  • Remote layout backends — point [ingest].docling_url / marker_url at docling-serve or
    marker_server. The GPU runs server-side, so klode keeps zero runtime dependencies. Preferred
    over a local install: remote conversions have a deadline that scales with the document, while the
    in-process backends have no wall-clock bound.
  • klode review --live-judge — the real rubric judge, behind an explicit flag. A config file is
    consent to choose a model, not to spend money; without the flag klode makes no network call
    whatever the settings say.
  • eval/extract_bakeoff.py — ranks backends against the rendered page by a paired comparison,
    and refuses to rank when no viable pairing exists. The committed result over 20 two-column papers
    is what earns docling its tier-3 slot and keeps marker out of the auto ladder.

How it was checked

Two audits, 137 findings. The second audited the code written to close the first, and found five
rows that claimed closure the behaviour had not reached. Every fix was mutation-checked — reverted,
and the new test confirmed to fail. Full notes in CHANGELOG.md.

Known limit, stated rather than implied: local in-process OCR has no wall-clock bound. Bounding
it needs a worker process that re-imports torch in the child; that was built and not shipped,
because it could not be tested against backends absent from the environment. Use a remote endpoint
where a deadline matters.