bindery-cli 0.41.0__tar.gz → 0.41.1__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 (44) hide show
  1. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/.github/workflows/publish.yml +7 -1
  2. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/CLAUDE.md +1 -1
  3. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/PKG-INFO +1 -1
  4. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/patchnotes.md +38 -0
  5. bindery_cli-0.41.1/project.done +57 -0
  6. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/pyproject.toml +1 -1
  7. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/roadmap.md +32 -0
  8. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/__init__.py +1 -1
  9. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/epub.py +5 -2
  10. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/transforms.py +21 -4
  11. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_transforms.py +32 -0
  12. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/uv.lock +1 -1
  13. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/.github/workflows/ci.yml +0 -0
  14. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/.gitignore +0 -0
  15. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/AGENTS.md +0 -0
  16. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/LICENSE +0 -0
  17. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/README.md +0 -0
  18. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/docs/screenshots/library-sweep.png +0 -0
  19. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/logo.svg +0 -0
  20. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/plugin/__init__.py +0 -0
  21. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/run_tests.sh +0 -0
  22. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/scripts/FastSweep.java +0 -0
  23. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/scripts/build_plugin.py +0 -0
  24. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/scripts/fast_sweep.py +0 -0
  25. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/spec.md +0 -0
  26. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/__main__.py +0 -0
  27. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/audit.py +0 -0
  28. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/cli.py +0 -0
  29. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/library.py +0 -0
  30. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/pagination.py +0 -0
  31. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/reserialize.py +0 -0
  32. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/validate.py +0 -0
  33. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/src/bindery/watermark.py +0 -0
  34. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/testing_facility/books_to_fix/.gitkeep +0 -0
  35. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_audit.py +0 -0
  36. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_cli.py +0 -0
  37. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_epub.py +0 -0
  38. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_library.py +0 -0
  39. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_pagination.py +0 -0
  40. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_plugin.py +0 -0
  41. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_reserialize.py +0 -0
  42. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_validate.py +0 -0
  43. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_version.py +0 -0
  44. {bindery_cli-0.41.0 → bindery_cli-0.41.1}/tests/test_watermark.py +0 -0
@@ -74,7 +74,13 @@ jobs:
74
74
  # The tag message IS the release's patchnotes entry (verbatim, per the
75
75
  # tagging procedure), so the release body is extracted from the tag
76
76
  # object rather than paraphrased here. Without it the release page
77
- # renders blank beside the zip.
77
+ # renders blank beside the zip. The checkout is depth-1 and carries no
78
+ # tag objects, so the tag ref is fetched explicitly first (without
79
+ # this, `git tag -l` lists nothing and the release body silently falls
80
+ # back to the commit message).
81
+ - name: Fetch the tag object
82
+ run: git fetch --no-tags origin "refs/tags/${GITHUB_REF_NAME}:refs/tags/${GITHUB_REF_NAME}"
83
+
78
84
  - name: Extract the release body from the tag message
79
85
  run: git tag -l --format='%(contents)' "${GITHUB_REF_NAME}" > release-body.md
80
86
 
@@ -82,7 +82,7 @@ Born from the 2026 library audit (see the user memory `calibre-library-epubcheck
82
82
 
83
83
  ## Layout
84
84
 
85
- - `src/bindery/transforms.py`: pure `str -> (str, int)` text transforms (including `strip_broken_tags`). Since v0.32.0: `strip_attrs_in_start_tags` anchors attribute edits on the quote-aware start-tag matcher, `_outside_protected` forwards arguments (context-carrying transforms can be decorated), `fix_id_colons` matches only the bare `id` attribute and only internal fragments (`fix_ncx_src_fragments` carries renames into the NCX), `fix_ncx_playorder` is anchored to `<navPoint>` start tags, and the css selector boundary covers namespaced and functional selector forms. Since v0.37.0: `fix_ncx_playorder` strips quotes before its already-correct comparison (regex groups carry quote characters; comparing quoted values against bare numbers counted phantom fixes on every sequential NCX and rewrote single-quoted correct attributes to double quotes).
85
+ - `src/bindery/transforms.py`: pure `str -> (str, int)` text transforms (including `strip_broken_tags`). Since v0.32.0: `strip_attrs_in_start_tags` anchors attribute edits on the quote-aware start-tag matcher, `_outside_protected` forwards arguments (context-carrying transforms can be decorated), `fix_id_colons` matches only the bare `id` attribute and only internal fragments (`fix_ncx_src_fragments` carries renames into the NCX), `fix_ncx_playorder` is anchored to `<navPoint>` start tags, and the css selector boundary covers namespaced and functional selector forms. HAZARD (the 0.41.0 spin): the quote-aware tag-prefix alternation (double-quoted | single-quoted | [^>]) OVERLAPS ([^>] matches quotes too), so a plain `*` re-partitions exponentially on failing candidates; every such loop must carry the possessive `*+` (re 3.11+, the plugin's minimum Calibre). Since v0.37.0: `fix_ncx_playorder` strips quotes before its already-correct comparison (regex groups carry quote characters; comparing quoted values against bare numbers counted phantom fixes on every sequential NCX and rewrote single-quoted correct attributes to double quotes).
86
86
  - `src/bindery/pagination.py`: the opt-in lossy page-number strip (runhead detection, page-layer decision, block-centric removal/merge, safety nets).
87
87
  - `src/bindery/watermark.py`: the opt-in lossy watermark strip (anchored and anchorless signature removal).
88
88
  - `src/bindery/reserialize.py`: structural repair via `html5lib`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bindery-cli
3
- Version: 0.41.0
3
+ Version: 0.41.1
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
@@ -1,4 +1,42 @@
1
1
  # bindery-cli Patch Notes
2
+ ## v0.41.1 (2026-09-15)
3
+
4
+ ### The 0.41.0 CPU-spin regression: possessive bounds on the quote-aware tag matchers
5
+
6
+ - **The regression.** `repair --all` spun CPU indefinitely on real
7
+ books (found on a Hackett Aristotle *Physics* whose 843k-character
8
+ back-matter document is full of apostrophes and span candidates
9
+ whose closer never comes). 0.40.0 repaired the same book in seconds.
10
+ Brandon bisected it to 0.41.0 and root-caused it to the wheel diff:
11
+ the 0.41.0 quote-aware rewrite of `unwrap_block_in_inline`'s start
12
+ tag replaced the linear prefix with an overlapping alternation
13
+ (double-quoted | single-quoted | any-non-bracket), and because the
14
+ non-bracket branch also matches quote characters, every failing
15
+ candidate let the engine re-partition the prefix across the branches
16
+ exponentially. The flag-bisect had looked interaction-shaped because
17
+ earlier transforms move where the failing candidates sit; the regex
18
+ was input-sensitive, not the flags broken.
19
+ - **The fix: possessive quantifiers.** The repeated group is now
20
+ possessive (`*+`, Python 3.11's `re`, the same floor as the plugin's
21
+ minimum Calibre): the engine commits to its first-alternative
22
+ consumption, which is the nearest-paired-quote path ending at the
23
+ first unquoted `>` (the correct tag end), and never re-partitions.
24
+ Failure becomes linear; successful matches are unchanged. Applied to
25
+ all three patterns the 0.41.0 commit rewrote:
26
+ `unwrap_block_in_inline` (the spinner), `unwrap_illegal_tags`, and
27
+ `downgrade_epub3_tags`. The pre-existing quote-aware matchers
28
+ (`_VOID_RE`, the OPF tag matchers) were tested against the same
29
+ shape and do not spin; their audit is a recorded follow-up box.
30
+ - **Verified on the fixture.** The transform went from still-spinning
31
+ at 15 seconds to 0.01 seconds; the recorded reproduction
32
+ (`repair --all --force`) completes in 7.7 seconds (epubcheck's own
33
+ time) with output identical to the 0.40.0 shape
34
+ (cover_meta_repointed:1, pagelist_class_added:1, ncx_uid_synced).
35
+ Two regression tests pin it: the spin shape itself (a span followed
36
+ by a 40k-character apostrophe run with no `>`: the old pattern
37
+ provably cannot finish it) and the quote-aware function the rewrite
38
+ was for (a `>` inside an attribute value).
39
+
2
40
  ## v0.41.0 (2026-09-15)
3
41
 
4
42
  ### The final-audit blitz: robust analyzers, the stub-docs strip, and a privacy untrack
@@ -0,0 +1,57 @@
1
+ # bindery-cli: project.done
2
+
3
+ Completed: 2026-09-15 (THE FINAL BLITZ, repo agent lane)
4
+ Range: 2b9f24e (analyzer trio) through f47fc8f (ci tag-fetch fix), on main,
5
+ pushed; CI green throughout; v0.41.0 tagged and published to PyPI.
6
+
7
+ ## What shipped
8
+
9
+ - The analyzer-robustness trio: spineless-EPUB verdicts, per-book try
10
+ coverage, the corrupt-container CORRUPT verdict, the DRM false-EMPTY fix.
11
+ - The phase3 aggregate-summary fix (applied/equal states only,
12
+ rejected_projection line, Ghost Brigades regression test).
13
+ - Six features: `bindery doctor`, `repair --json FILE`, `--strip-stub-docs`
14
+ (the lossy lane's live fixture, Brandon-approved), plugin log rotation,
15
+ `--backup-keep N` ring, the fast_sweep count-scale pin.
16
+ - The robustness LOW batch (quote-aware matchers, raw-name resolution,
17
+ atomic backups, rglob filter, mojibake-uid guard, daemon tempdir sweep,
18
+ apply-failure accounting, relative_to guard).
19
+ - The privacy untrack of testing_facility/top500candidates/REPORT.md
20
+ (forward-only; Brandon's ruling).
21
+ - The GitHub batch: release bodies via body_path + three backfills, the
22
+ description rewrite, [project.urls], topics swap, wiki off, the
23
+ redundant suite run removed, concurrency blocks, checkout alignment,
24
+ the release action SHA-pinned; plus a v*.*.* tag ruleset (admin bypass)
25
+ and the pypi environment restricted to tag pushes (Brandon's rulings).
26
+ - Docs truth batches: one canonical repair inventory in spec.md, the audit
27
+ exit-code contract, the epubcheck install paragraph, the flag-list move,
28
+ the mimetype-fix counts, the degrades-safely correction, the
29
+ comment/docstring truth pass, the em-dash sweep and the "honest" tic.
30
+ - Dead code removed on a recorded go: validate's `_daemon` alias, cli's
31
+ `done_count`, audit's `sys.path.insert`.
32
+ - Hygiene: REPORT-12-Sept.md retired per the BINDERY_REPORT precedent; the
33
+ derived litter deleted (test_facility/__pycache__, FastSweepExtract.class,
34
+ venv/ + .venv_ci/).
35
+ - Tests: 445 -> 474, all green; ruff 0.16.2 clean.
36
+
37
+ ## Reopen conditions (recorded future work)
38
+
39
+ 1. top500candidates/REPORT.md history strip: declined (forward-only only).
40
+ If ever wanted: filter-repo the file out, re-tag every affected release
41
+ verbatim, disable publish.yml around the push (force-push class).
42
+ 2. Stranger-facing XML hardening (dependency ask pending): a stdlib
43
+ per-entry size cap in load_book plus defusedxml-style entity-expansion
44
+ gating. Trigger: a real 3.12/3.13 stranger audience. Roadmap boxed.
45
+ 3. MobileRead listing for the Bindery Repair plugin: Brandon's manual
46
+ posting step; roadmap boxed with the material listed.
47
+ 4. README screenshot regeneration against a synthetic library: pending
48
+ Brandon's call; the current PNG still shows real library rows.
49
+ 5. Boxed M feature set: --encode-url-spaces entry renames (280-book
50
+ PKG-010 class), the cover advisory analyzer, the audit-refresh
51
+ deep-dive, the NCX<->nav drift detector.
52
+ 6. Boxed internal refactors: RepairFlags dataclass for the triplicated
53
+ kwarg signature; shared roman/arabic number helpers for pagination.py
54
+ and audit.py.
55
+ 7. Cross-repo note under decision #117: cquarry, CalibreQuarry, and
56
+ vir-tui should mirror the tag ruleset + pypi environment policy this
57
+ repo adopted.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bindery-cli"
7
- version = "0.41.0"
7
+ version = "0.41.1"
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 = [
@@ -1849,3 +1849,35 @@ four interpreter families.
1849
1849
  stdlib-only rule and adds a vendored plugin dep): trigger is a
1850
1850
  real 3.12/3.13 stranger audience, not a hypothetical.
1851
1851
  - [x] **vir-tui floor >=2.5.0 + lock refresh** (2026-09-14, no release cut: upstream 2.4.0/2.5.0 are additive terminal-safety and session-awareness releases; only the vir-tui resolution moved in the lock, the cquarry lag rule is untouched). *(Mention the floor bump in the next release's patchnotes.)*
1852
+
1853
+ - [x] **REGRESSION (0.41.0) FIXED in v0.41.1: the repair --all CPU
1854
+ spin on the Physics fixture was catastrophic backtracking in the
1855
+ rewritten `unwrap_block_in_inline` regex** (SHIPPED 2026-09-15,
1856
+ 176906f): the quote-aware alternation's branches overlap ([^>]
1857
+ also matches quotes), so failing candidates re-partitioned the
1858
+ prefix exponentially. All three patterns the 0.41.0 commit
1859
+ rewrote (`unwrap_block_in_inline`, `unwrap_illegal_tags`,
1860
+ `downgrade_epub3_tags`) are now possessive-bounded (`*+`, re
1861
+ 3.11+ = the plugin's minimum Calibre floor): the committed
1862
+ first-alternative path is the correct tag end, failure is
1863
+ linear, successful matches are unchanged. Verified on Brandon's
1864
+ fixture: the transform went from still-spinning at 15s to
1865
+ 0.01s; the recorded repro (`repair --all --force`) completes in
1866
+ 7.7s with output identical to the 0.40.0 shape. Regression
1867
+ tests: the spin shape (the old pattern provably cannot finish
1868
+ it) and the quote-aware function. Root-cause note preserved from
1869
+ the report: the flag-bisect looked interaction-shaped because
1870
+ earlier transforms move where failing candidates sit; the regex
1871
+ was input-sensitive, not the flags broken. Unpin 0.40.0 once
1872
+ 0.41.1 is installed.
1873
+ - [ ] **Audit the remaining quote-aware tag matchers for the same
1874
+ overlap ambiguity** (follow-up from the 0.41.1 hotfix): _VOID_RE
1875
+ and the OPF matchers (cover meta, guide reference,
1876
+ spine/item/itemref) share the overlapping-alternation shape but
1877
+ were tested against the spin shape and do not blow up (the lazy
1878
+ loop and always-matching candidates behave differently;
1879
+ _VOID_RE's failure mode on a quote-run with no reachable `>` is
1880
+ quadratic at worst, and it can match surprisingly far across
1881
+ quoted spans, pre-existing since 0.40.0). Decide per matcher:
1882
+ possessive-bound for uniformity, or document why the shape is
1883
+ safe there. Never a third grammar copy.
@@ -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.41.0"
14
+ VERSION = "0.41.1"
15
15
  __version__ = VERSION
16
16
 
17
17
  from .epub import RepairReport, repair_epub # noqa: E402
@@ -588,9 +588,12 @@ def downgrade_epub3_tags(
588
588
  return f"</{new}>"
589
589
 
590
590
  # quote-aware attrs group (same shape as the manifest matchers): a
591
- # `>` inside an attribute value must not end the tag early
591
+ # `>` inside an attribute value must not end the tag early. The
592
+ # branches overlap ([^>] also matches quotes), so the possessive
593
+ # `*+` bounds the re-partitioning that spun CPU on failing
594
+ # candidates in 0.41.0 (see transforms.unwrap_block_in_inline).
592
595
  text = re.sub(
593
- rf"""<{tag}\b((?:"[^"]*"|'[^']*'|[^>])*)>""",
596
+ rf"""<{tag}\b((?:"[^"]*"|'[^']*'|[^>])*+)>""",
594
597
  repl_open,
595
598
  text,
596
599
  flags=re.IGNORECASE,
@@ -374,8 +374,19 @@ def unwrap_block_in_inline(s: str) -> tuple[str, int]:
374
374
  return m.group(1)
375
375
 
376
376
  s, n = re.subn(
377
- r"""<span(?:(?:"[^"]*"|'[^']*'|[^>])*)>\s*"""
378
- r"""(<(div|p|blockquote)(?:(?:"[^"]*"|'[^']*'|[^>])*)>.*?</\2>)\s*</span>""",
377
+ # The quote-aware branches OVERLAP ([^>] also matches quote
378
+ # characters), so a plain `*` lets the engine re-partition the start
379
+ # tag exponentially across them on every failing candidate: on a
380
+ # 843k-char doc full of <span>s whose closer never comes, 0.41.0
381
+ # spun CPU indefinitely (the Physics-fixture regression,
382
+ # roadmap 2026-09-15). The possessive `*+` commits to the
383
+ # first-alternative consumption (ending at the first unquoted `>`,
384
+ # which is the correct tag end) and never re-partitions: linear on
385
+ # failure, same matches on success. `re` has had possessive
386
+ # quantifiers since 3.11, the same floor as the plugin's minimum
387
+ # Calibre.
388
+ r"""<span(?:(?:"[^"]*"|'[^']*'|[^>])*+)>\s*"""
389
+ r"""(<(div|p|blockquote)(?:(?:"[^"]*"|'[^']*'|[^>])*+)>.*?</\2>)\s*</span>""",
379
390
  repl,
380
391
  s,
381
392
  flags=re.IGNORECASE | re.DOTALL,
@@ -503,9 +514,15 @@ def unwrap_illegal_tags(
503
514
  continue
504
515
  # quote-aware start-tag match (same shape as _VOID_RE): a `>` inside
505
516
  # an attribute value would otherwise end the match early and mangle
506
- # the tag into a malformed edit the gate then rejects
517
+ # the tag into a malformed edit the gate then rejects. The branches
518
+ # overlap ([^>] also matches quotes), so the possessive `*+` bounds
519
+ # the re-partitioning that spun CPU on failing candidates in 0.41.0
520
+ # (see unwrap_block_in_inline).
507
521
  s, n1 = re.subn(
508
- rf"""<{tag}\b(?:(?:"[^"]*"|'[^']*'|[^>])*)>""", "", s, flags=re.IGNORECASE
522
+ rf"""<{tag}\b(?:(?:"[^"]*"|'[^']*'|[^>])*+)>""",
523
+ "",
524
+ s,
525
+ flags=re.IGNORECASE,
509
526
  )
510
527
  s, n2 = re.subn(rf"</{tag}\s*>", "", s, flags=re.IGNORECASE)
511
528
  count += n1 + n2
@@ -389,6 +389,38 @@ class TestProtectedSpans(unittest.TestCase):
389
389
  out, n = unwrap_illegal_tags("<p>a<w>x</w>b<st>c</st></p>")
390
390
  self.assertEqual((out, n), ("<p>axbc</p>", 4))
391
391
 
392
+ def test_span_wrapping_block_with_gt_in_attribute_unwraps(self):
393
+ # the reason the start-tag match is quote-aware at all: the `>` of
394
+ # "a>b" is attribute content, and the span still wraps a block
395
+ text = '<p><span title="a>b"><div class="k">x</div></span>tail</p>'
396
+ out, n = unwrap_block_in_inline(text)
397
+ self.assertEqual((out, n), ('<p><div class="k">x</div>tail</p>', 1))
398
+
399
+ def test_failing_span_candidate_cannot_backtrack_explode(self):
400
+ # REGRESSION (0.41.0, the Physics fixture): the quote-aware prefix
401
+ # branches overlap ([^>] also matches quote characters), so in a
402
+ # long apostrophe-rich run with no `>` after a <span, the engine
403
+ # re-partitioned the prefix exponentially on the failing match and
404
+ # spun CPU indefinitely (an 843k-char back-matter document never
405
+ # finished). The possessive `*+` commits to the first partition and
406
+ # makes failure linear. This shape is the guard: the old pattern
407
+ # cannot finish this function call at all.
408
+ import signal
409
+ import time
410
+
411
+ run = "don't can't won't it's a>kind of prose, isn't it " * 2000
412
+ doc = "<p><span class='broken " + run + "and no closer.</p>"
413
+ signal.alarm(20) # belt: fail the test, never hang the suite
414
+ try:
415
+ t0 = time.perf_counter()
416
+ out, n = unwrap_block_in_inline(doc)
417
+ elapsed = time.perf_counter() - t0
418
+ finally:
419
+ signal.alarm(0)
420
+ self.assertEqual(n, 0)
421
+ self.assertEqual(out, doc) # nothing matched: byte-for-byte
422
+ self.assertLess(elapsed, 5.0)
423
+
392
424
  def test_illegal_tag_with_gt_in_attribute_value_is_removed_whole(self):
393
425
  # a bare [^>]* matcher ended the start tag at the `>` inside the
394
426
  # attribute value, mangled the edit, and the gate rejected the run
@@ -8,7 +8,7 @@ resolution-markers = [
8
8
 
9
9
  [[package]]
10
10
  name = "bindery-cli"
11
- version = "0.41.0"
11
+ version = "0.41.1"
12
12
  source = { editable = "." }
13
13
  dependencies = [
14
14
  { name = "cquarry", marker = "python_full_version >= '3.14'" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes