bindery-cli 0.47.0__tar.gz → 0.47.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/CLAUDE.md +3 -1
  2. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/PKG-INFO +9 -1
  3. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/README.md +8 -0
  4. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/patchnotes.md +60 -0
  5. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/pyproject.toml +1 -1
  6. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/__init__.py +1 -1
  7. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/cli.py +509 -273
  8. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_cli.py +130 -0
  9. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/uv.lock +1 -1
  10. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/.github/FUNDING.yml +0 -0
  11. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/.github/workflows/ci.yml +0 -0
  12. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/.github/workflows/publish.yml +0 -0
  13. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/.gitignore +0 -0
  14. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/AGENTS.md +0 -0
  15. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/LICENSE +0 -0
  16. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/docs/screenshots/library-sweep.png +0 -0
  17. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/logo.svg +0 -0
  18. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/plugin/__init__.py +0 -0
  19. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/project.done +0 -0
  20. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/roadmap.md +0 -0
  21. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/run_tests.sh +0 -0
  22. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/scripts/FastSweep.java +0 -0
  23. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/scripts/build_plugin.py +0 -0
  24. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/scripts/fast_sweep.py +0 -0
  25. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/spec.md +0 -0
  26. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/__main__.py +0 -0
  27. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/audit.py +0 -0
  28. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/epub.py +0 -0
  29. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/library.py +0 -0
  30. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/pagination.py +0 -0
  31. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/reserialize.py +0 -0
  32. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/transforms.py +0 -0
  33. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/validate.py +0 -0
  34. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/src/bindery/watermark.py +0 -0
  35. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_audit.py +0 -0
  36. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_comment_hyphens.py +0 -0
  37. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_cover_tocdrift.py +0 -0
  38. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_epub.py +0 -0
  39. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_flags_real_core.py +0 -0
  40. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_flags_wiring.py +0 -0
  41. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_library.py +0 -0
  42. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_matcher_hardening.py +0 -0
  43. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_pagination.py +0 -0
  44. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_plugin.py +0 -0
  45. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_reserialize.py +0 -0
  46. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_space_renames.py +0 -0
  47. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_transforms.py +0 -0
  48. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_validate.py +0 -0
  49. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_version.py +0 -0
  50. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_watermark.py +0 -0
  51. {bindery_cli-0.47.0 → bindery_cli-0.47.2}/tests/test_xml_hardening.py +0 -0
@@ -118,7 +118,9 @@ Born from the 2026 library audit (see the user memory `calibre-library-epubcheck
118
118
  - `src/bindery/epub.py`: archive rewrite, NCX uid sync, RepairReport, mismatch detection, and the opt-in structural-repair plumbing (including the CSS precondition scan). Since v0.46.0 the opt-in selection is the `RepairFlags` dataclass (one field per flag, all off by default; the all-off instance IS the default pass): `repair_epub(src, dst, flags)` and `process_book(..., flags)` take one object instead of a ~25-kwarg signature, the EPUB2-targeted fixes' version gate swaps in a `dataclasses.replace` copy (never mutating the caller's instance), and the plugin's bare `repair_epub(src, dst)` call constructs exactly the all-off default pass. Since v0.38.0/v0.39.0 (Phase 16): `generate_container` + the `--fix-container` gateway (insert after mimetype when missing, replace in-loop when stale; constant-epoch timestamp keeps bytes deterministic), `prune_dangling_edges` (prune_missing_manifest_items returns the pruned ids and the edges that pointed at them (spine@toc, media-overlay, fallback, EPUB2 cover meta) are rewritten so pruning cannot manufacture the regression the gate rejects), and `fix_manifest_media_types` (extension map + magic-byte confirmation via a `peek` callable over the open source zip; jpg/png/gif only), and `fix_cover_meta` (the ruling's deterministic half: dangling EPUB2 cover meta re-pointed from the guide's cover reference when exactly one manifest item carries that file, removed otherwise; accepted under no_worse like the lossy strips because cover wiring is invisible to epubcheck).
119
119
  - `src/bindery/validate.py`: epubcheck wrapper, the `gate` (improvement) and `no_worse` (no-regression, for the lossy strips) acceptance bars.
120
120
  - `src/bindery/library.py`: Calibre walk, atomic replace, backups, and native format installation. Since v0.19.0 `CalibreIdResolver` resolves the book id from `metadata.db` through cquarry (one lazy path→id map per run; since v0.20.0 the map comes from `CalibreDB.format_path_index()`, re-normalized with `resolve().lower()` for the resolver's symlinked-directory and case-insensitive matching); the `(id)` directory-name regex is only the no-catalog fallback. Since v0.24.0 `install_format()` places the repaired file atomically and updates the `data` row through `cquarry.write.WritableCalibreDB` (since v0.31.0 via `set_format`, cquarry 1.17's sanctioned remove+add in one transaction); the external `calibredb` CLI is gone. Since v0.32.0 a guessed id drives a row update only when metadata.db corroborates it (`_verify_guess`: books row exists, file in the book's own directory, catalogued `data.name` when a row exists); anything else saves in place and leaves the catalog untouched: never reintroduce directory-name guessing as the primary source: renamed/mismatched directories would replace the wrong book. Since 2026-09-15 `make_backup` takes the opt-in `--backup-keep N` ring: at most N backup files per book, the author original `.bak` never deleted, newest content at the highest name; default remains an unbounded rotation.
121
- - `src/bindery/cli.py`: `repair` and `library` subcommands, including `--all` and `--install-to-calibre`; `repair --json` emits the library per-book record vocabulary (2026-09-15; since v0.45.0 every per-book record, the phase1 payload included, also carries the structured fix breakdown: the `fixes` dict plus `ncx_uid_synced`/`watermark_refusals` off the `Outcome`, and `_phase1_decisions` reads that data instead of substring-matching the rendered `summary`, which stays for human eyes; CalibreQuarry's lossy-consent mirror is the waiting consumer, floor `bindery>=0.45.0`); since v0.29.0 also the `run` verbs: `phase1` (pre-import vetting over loose files: the audit battery composed with one gated repair sweep; read-only until `--apply-lossy`, which IS the lossy-strip consent) and `phase3` (the scoped post-import apply step: `library --id --sweep --only all --apply --all --install-to-calibre` plus a pre/post summary; mechanically refuses unscoped sweeps with exit 2; since 2026-09-15 the summary sums applied/equal states only and carries rejected projections as `rejected_projection`). Plus `bindery doctor` (2026-09-15): the environment self-check that imports none of the VirInvictus stack, never raises, and always exits 0. Both run verbs take `--non-interactive` and surface open questions as `decisions_needed` in their JSON, never prompts. They drive the shipped `run_library` through the real parser, so no flag can drift between the verb and the subcommand it wraps. Since v0.46.0 `cli._flags_from_args(args)` is the one place the argparse names map onto `RepairFlags` fields (each `or --all`), shared by `run_library` and `run_repair`; the old per-verb ~25-kwarg blocks are gone. `library --workers N` (v0.30.0) parallelizes only the sweep's candidate pass, in windows of N consumed in order so `--limit` stays lazy; the repair phase is never parallel (shared workdir, atomic-replacement contract). Since v0.40.0 `main()` guards the VirInvictus stack: a `ModuleNotFoundError` naming `vir_tui`/`cquarry` (marker-gated to 3.14+ on the 3.12 floor) becomes one stderr line and trouble exit 2 instead of a traceback; anything else missing still raises.
121
+ - `src/bindery/cli.py`: `repair` and `library` subcommands, including `--all` and `--install-to-calibre`; `repair --json` emits the library per-book record vocabulary (2026-09-15; since v0.45.0 every per-book record, the phase1 payload included, also carries the structured fix breakdown: the `fixes` dict plus `ncx_uid_synced`/`watermark_refusals` off the `Outcome`, and `_phase1_decisions` reads that data instead of substring-matching the rendered `summary`, which stays for human eyes; CalibreQuarry's lossy-consent mirror is the waiting consumer, floor `bindery>=0.45.0`); since v0.29.0 also the `run` verbs: `phase1` (pre-import vetting over loose files: the audit battery composed with one gated repair sweep; read-only until `--apply-lossy`, which IS the lossy-strip consent) and `phase3` (the scoped post-import apply step: `library --id --sweep --only all --apply --all --install-to-calibre` plus a pre/post summary; mechanically refuses unscoped sweeps with exit 2; since 2026-09-15 the summary sums applied/equal states only and carries rejected projections as `rejected_projection`). Plus `bindery doctor` (2026-09-15): the environment self-check that imports none of the VirInvictus stack, never raises, and always exits 0. Both run verbs take `--non-interactive` and surface open questions as `decisions_needed` in their JSON, never prompts. They drive the shipped `run_library` through the real parser, so no flag can drift between the verb and the subcommand it wraps. Since v0.46.0 `cli._flags_from_args(args)` is the one place the argparse names map onto `RepairFlags` fields (each `or --all`), shared by `run_library` and `run_repair`; the old per-verb ~25-kwarg blocks are gone. Since v0.47.1 the help is two-level (the --help-repairs reference is
122
+ custom-rendered and colors itself with the CPython 3.14 argparse theme,
123
+ gated by argparse's own _colorize detection so the levels always agree) and the repair-flag inventory is one table: `_REPAIR_FLAG_TABLE` (dest + one-line short help + long reference text) registers the compact grouped `--help` (argparse argument groups, hand-short usage line) and `--help-repairs` renders the long descriptions (a custom argparse Action, deliberately NOT store_true so the wiring test's selection count ignores it); adding a flag means adding a table row. `library --workers N` (v0.30.0) parallelizes only the sweep's candidate pass, in windows of N consumed in order so `--limit` stays lazy; the repair phase is never parallel (shared workdir, atomic-replacement contract). Since v0.40.0 `main()` guards the VirInvictus stack: a `ModuleNotFoundError` naming `vir_tui`/`cquarry` (marker-gated to 3.14+ on the 3.12 floor) becomes one stderr line and trouble exit 2 instead of a traceback; anything else missing still raises.
122
124
  - `plugin/__init__.py` + `scripts/build_plugin.py`: the Bindery Repair Calibre plugin (v0.37.0; identity `Bindery Repair` / `bindery_repair`, approved and built per the scoped spec in roadmap.md Phase 3). The builder vendors `transforms.py`, `epub.py`, `pagination.py`, `watermark.py`, `reserialize.py` byte-identically into the zip (the zip root is a package, so their relative imports resolve unchanged; the suite's drift test pins the equality) with the version tuple substituted from the single-source VERSION; `publish.yml` attaches `BinderyRepair-v<VERSION>.zip` to each release. The plugin runs ONLY the default pass (epubcheck cannot gate inside Calibre), never raises, is byte-idempotent (zero fixes returns the original path), testzip()-verifies its output, logs one line per book, and takes JSON config via `site_customization` (`log`, `log_path`, `max_size_mb` default 150, `max_log_mb` rotation cap default 2, `epubcheck_path` experimental validation default OFF). Load it in tests through the `tests/test_plugin.py` stubs: the calibre namespace is installed in sys.modules and the package loads with a two-dir `__path__` (plugin dir first, then src/bindery) exactly like calibre's zipplugin loader shape. **Plugin/interpreter compatibility (v0.40.0):** v0.39.0 shipped PEP 758 unparenthesized except tuples, and the plugin died with a bare SyntaxError on every released Calibre (7.x and the whole 8.x series embed Python 3.11, 9.0+ embeds 3.14; from calibre's bypy sources.json, checked against the upstream clone). The core is now grammar-pinned: parenthesized except tuples everywhere, `ruff`'s `target-version = "py312"` pin (inference from requires-python would rewrite the parens back off), `tests/test_version.py`'s parse guard, and CI's `plugin-compat` job byte-compiling the built zip under 3.11/3.12/3.13/3.14. `minimum_calibre_version` is (7, 0, 0): the oldest series whose interpreter CI covers, replacing the hollow (2, 0, 0); `supported_platforms` stays `["linux"]` (developed and tested on Linux only). Any new syntax in the vendored modules must clear the guard before it ships.
123
125
  - `tests/`: transforms, end-to-end repair, atomic replace, pagination, watermarks, the audit analyzers (content/pagenumbers/emptytext/ocr/monolithic/completeness, archive integrity, spine classification), the library sweep and `CalibreIdResolver`, validate, reserialize, CLI wiring, the version pin and the syntax-floor grammar guard, and the Calibre plugin (build drift + run behavior under calibre stubs). Since the v0.46.0 follow-ups: `tests/test_flags_wiring.py` reflectively pins the RepairFlags inventory to the argparse dests and `_flags_from_args` mapping (a flag added without a field, a field without a flag, or a remapped line fails there), the all-off defaults, and the version gate's caller-immutability contract (test_epub's `TestVersionGateKeepsFlagsIntact`); `tests/test_comment_hyphens.py` carries the RSC-016 fix; `tests/test_flags_real_core.py` drives every repair flag from argv through the unmocked core (--no-validate; the gate itself stays pinned by the mocked-oracle tests and runs for real wherever epubcheck exists). All three ride CI's core-compat 3.12/3.13 leg; the hand-listed module list there must gain an entry for every new stack-free test module.
124
126
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bindery-cli
3
- Version: 0.47.0
3
+ Version: 0.47.2
4
4
  Summary: Audit EPUB body text and repair broken EPUBs with deterministic, epubcheck-gated fixes; atomic in-place replacement in a Calibre library.
5
5
  Project-URL: Homepage, https://github.com/VirInvictus/bindery-cli
6
6
  Project-URL: Repository, https://github.com/VirInvictus/bindery-cli
@@ -203,6 +203,14 @@ vetting, phase-3 scoped post-import repair) for a calling agent, and `bindery do
203
203
  checks the installation (see Install).
204
204
  The sections below take each in turn.
205
205
 
206
+ Help is two-level: `bindery repair --help` (or `library --help`) lists the
207
+ shared repair flags grouped with one-line descriptions, and
208
+ `bindery repair --help-repairs` prints the full reference carrying every
209
+ flag's long rationale. On a terminal both are ANSI-colored with the same
210
+ theme Python 3.14's argparse uses; piped output stays plain, and `NO_COLOR`,
211
+ `FORCE_COLOR`, and `TERM=dumb` are honored exactly as `python --help` honors
212
+ them.
213
+
206
214
  ## Auditing
207
215
 
208
216
  bindery-cli's auditing tool inspects EPUB body text for non-schema flaws that epubcheck cannot catch. It extracts and analyzes the visible text to detect content issues, producing console reports that can be used to filter your library or feed into `bindery repair`.
@@ -176,6 +176,14 @@ vetting, phase-3 scoped post-import repair) for a calling agent, and `bindery do
176
176
  checks the installation (see Install).
177
177
  The sections below take each in turn.
178
178
 
179
+ Help is two-level: `bindery repair --help` (or `library --help`) lists the
180
+ shared repair flags grouped with one-line descriptions, and
181
+ `bindery repair --help-repairs` prints the full reference carrying every
182
+ flag's long rationale. On a terminal both are ANSI-colored with the same
183
+ theme Python 3.14's argparse uses; piped output stays plain, and `NO_COLOR`,
184
+ `FORCE_COLOR`, and `TERM=dumb` are honored exactly as `python --help` honors
185
+ them.
186
+
179
187
  ## Auditing
180
188
 
181
189
  bindery-cli's auditing tool inspects EPUB body text for non-schema flaws that epubcheck cannot catch. It extracts and analyzes the visible text to detect content issues, producing console reports that can be used to filter your library or feed into `bindery repair`.
@@ -1,5 +1,65 @@
1
1
  # bindery-cli Patch Notes
2
2
 
3
+ ## v0.47.2 (2026-10-06)
4
+
5
+ ### The --help-repairs reference keeps argparse's color
6
+
7
+ The lattice-music lesson, landed here the same day: Python 3.14's argparse
8
+ colors `-h` on terminals by itself, so the argparse-rendered surfaces (the
9
+ top-level help and `-h` for every verb) kept their theme through the
10
+ v0.47.1 restructure untouched. The one gap was the page v0.47.1 added: the
11
+ custom-rendered `--help-repairs` reference printed plain, silently losing
12
+ the colorization every other surface has (the exact regression
13
+ lattice-music repaired in its 6b88a52).
14
+
15
+ * The reference now paints with the exact CPython 3.14 default theme
16
+ (bold blue headings, magenta prog, bold cyan long options; read off real
17
+ 3.14 output, so the page reads like the argparse-rendered `-h` beside
18
+ it) through a single layout path whose disabled style emits empty codes.
19
+ * The color gate IS argparse's own detection (`_colorize.can_colorize` on
20
+ 3.14+; plain False below 3.14, where argparse has no colorization to
21
+ match), so the two help levels can never disagree: verified under a pty
22
+ that `--help-repairs` carries the same code vocabulary as `-h` and that
23
+ `TERM=dumb` and `NO_COLOR` silence both, and that piped output stays
24
+ plain for README embedding and tests.
25
+ * Four new tests: the theme codes, the strip invariant (colored, codes
26
+ stripped == plain, byte for byte), pipe safety, and gate tracking.
27
+ TestHelpLayout is now nine; suite: 624.
28
+
29
+ ## v0.47.1 (2026-10-06)
30
+
31
+ ### The two-level help: compact grouped --help, full --help-repairs reference
32
+
33
+ The per-verb help had grown into a wall: a 16-line usage line listing all 27
34
+ repair flags, then roughly 30 paragraph-length descriptions in one flat
35
+ options list with the safe, structural, and lossy classes visually
36
+ undifferentiated, and the shared set printed a third time in the top-level
37
+ help. Flag names, semantics, and the argparse surface are untouched
38
+ (CalibreQuarry's consent mirror drives the shipped parser); this release
39
+ reorganizes how they are presented.
40
+
41
+ * One inventory, two renderings: the repair flags now live in a single
42
+ table (dest, one-line short help, long reference text) that registers
43
+ the compact `--help` into titled argparse groups and renders
44
+ `--help-repairs` as the full reference with every flag's long
45
+ description. Structural repairs are grouped by defect class (markup and
46
+ nesting; references and resources; package wiring), and adding a flag
47
+ now means adding a table row.
48
+ * Short usage lines replace the flag wall (`bindery repair [options] path
49
+ [output]`), repair and library carry examples epilogs, and the
50
+ top-level help becomes a verb list plus a pointer to the two levels.
51
+ * The four lossy strips are consistently `LOSSY:`-prefixed (two of four
52
+ carried it before) and live in their own warning-titled section ("these
53
+ delete converter-injected content; accepted on a no-worse bar").
54
+ * library's own 13 flags are grouped (run control, candidate selection,
55
+ backups, output and library integration) instead of one flat list.
56
+ * `--help-repairs` is a custom argparse action, deliberately not
57
+ store_true, so the wiring test's repair-selection count ignores it;
58
+ TestHelpLayout (five tests) pins the usage lines, group titles, LOSSY
59
+ prefixes, reference content, and that guarantee.
60
+
61
+ Tests: 620 (up from 615).
62
+
3
63
  ## v0.47.0 (2026-10-06)
4
64
 
5
65
  ### The intake-wave batch: five repair surfaces from five open issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bindery-cli"
7
- version = "0.47.0"
7
+ version = "0.47.2"
8
8
  description = "Audit EPUB body text and repair broken EPUBs with deterministic, epubcheck-gated fixes; atomic in-place replacement in a Calibre library."
9
9
  readme = "README.md"
10
10
  classifiers = [
@@ -11,7 +11,7 @@ Calibre plugin built from this core.
11
11
  Single source of truth for the version. pyproject.toml must match VERSION below.
12
12
  """
13
13
 
14
- VERSION = "0.47.0"
14
+ VERSION = "0.47.2"
15
15
  __version__ = VERSION
16
16
 
17
17
  from .epub import RepairReport, repair_epub # noqa: E402