flowmark 0.4.0

A Markdown auto-formatter for clean diffs and semantic line breaks
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# Changelog

All notable changes to this project will be documented in this file.

## [Unreleased][unreleased]

## [0.4.0][] (parity: flowmark-py 0.8.0)

Minor release syncing the expanded Python v0.8.0 compatibility surface and hardening the
multi-channel release path.
The 527-case shared contract has 495 exact passes and 32 inherited CommonMark
divergences, with no new divergence introduced by this release.

### Formatter behavior

- Preserve recognized math, code spans, fenced and indented code, and opaque
  Markdown-extension regions from the shared Python corpus as exact normalized-source
  slices, including nested-container fences, GitLab references, and multiline
  blockquotes.
- Keep fence info-string suffixes and numbered-looking code bytes intact, and stop
  inline preservation scopes at setext-heading boundaries.
- Measure escaped preservation sentinels at their rendered width so narrow wrapping
  remains stable and idempotent.
- Return normalized input unchanged if a parser or renderer violates preservation-token
  invariants, instead of aborting the process.
- Match the Python `reformat_text()` contract by retaining Markdown indentation rather
  than implicitly dedenting it.
  Direct `fill_markdown(..., dedent_input = true, ...)` remains the explicit
  docstring-oriented opt-in.

### CLI behavior

- Append `.orig` for in-place backups instead of replacing the input extension with
  `.bak`.
- Allow one direct file or standard input to write to `--output`; multiple inputs still
  reject a single output path.
- Reject invalid UTF-8 with exit status 2 instead of inserting replacement characters,
  and include the offending path for named inputs.
- Report missing or invalid explicit paths with their actual operating-system error and
  exit status 2.
- Document the shared exit-status contract: 0 for success, 1 for expected command-level
  conditions, and 2 for input or processing errors.

### Testing and porting

- Consume the Python repository’s language-neutral conformance manifest, golden files,
  reference documents, CommonMark corpus, and tryscript fixtures through a pinned
  submodule.
- Require exact stdout, stderr, exit status, filesystem effects, and idempotence without
  executing Python during Rust tests.
- Gate generated fixed-point behavior with a bidirectional seed-and-mode ledger, so new
  failures and stale known failures both stop CI.
- Embed the requested release tag in every archive binary and reject a real release tag
  that does not match `Cargo.toml`, preventing mislabeled immutable artifacts.

## [0.3.2][] (parity: flowmark-py 0.7.2)

Patch release completing the cross-agent Flowmark skill distribution while keeping
formatter output byte-identical to v0.3.1.

### Agent skill

- **Complete project setup guidance:** `--skill` and `--install-skill` now include the
  bundled `references/project-setup.md` guide for repository migration, Makefile
  integration, auto-fixing commit hooks, and disabling competing Markdown formatters.
- **Canonical skill ownership:** the main
  [Flowmark repository]https://github.com/jlevy/flowmark owns the public skill and
  documentation installed by `npx skills add`; this Rust package keeps a verified
  runtime mirror for CLI compatibility.
- **Pinned Rust runner:** generated skill artifacts bootstrap the released
  `flowmark-rs==0.3.2` package.

### Publishing reliability

- PyPI publication is rerun-safe: already-published versions are detected before upload,
  and wheel validation checks out the source scripts before inspecting artifacts.

## [0.3.1][] (parity: flowmark-py 0.7.2)

Patch release syncing the Python flowmark parity surface from v0.7.0 to v0.7.2 (upstream
patch releases v0.7.1 + v0.7.2) and porting the cross-agent agent-skill install.
CLI formatting stays byte-identical to the Python reference.

### Formatter behavior changes

- **Multi-line HTML comments preserved (upstream #35):** a standalone `<!-- ... -->`
  block that spans multiple lines keeps its interior line breaks verbatim instead of
  being reflowed onto one line, including inside blockquotes and list items.
- **`--width 0` whitespace normalization:** the no-wrap path now collapses internal runs
  of whitespace to single spaces (matching the `width > 0` path), so repeated formatting
  at `--width 0` is idempotent.

### CLI changes

- **`--check` flag:** validate-only mode that writes nothing and exits non-zero if any
  file would be reformatted, printing `Would reformat: <file>` to stderr.
  Useful for CI and pre-commit.
  Honors `--auto` (checks the full auto-format transform).
- **`--force-exclude` applies to explicitly-named files:** exclusion patterns (including
  multi-component and root-anchored `.flowmarkignore` entries like `/docs/api/`) now
  apply to files named directly on the command line, matching Black/Ruff and the Python
  reference. Explicitly-named files still override exclusions by default (without the
  flag).
- **Cross-agent skill install (`--install-skill --surfaces`):** installs the flowmark
  agent skill project-locally across `.agents/skills/` (portable), `.claude/skills/`
  (Claude Code), and an `AGENTS.md` block by default; `--surfaces` selects a subset
  (`portable`, `claude`, `agents-md`, `all`). Installed artifacts are byte-identical to
  the Python build’s.

### Fixes

- **Task-list checkbox spacing:** no double space after `- [ ]` / `- [x]`; idempotent
  under repeated formatting (upstream #42).
- **Reference-image inlining:** every reference-image form (`![alt][label]`, `![alt][]`,
  `![alt]`, and badge patterns) renders as the inline `![alt](url)` form, pinned by
  tests.
- **Skill runner pin on dev builds:** `--skill` / `--install-skill` pin the published
  `flowmark-rs` discovery release on unpublished dev builds rather than an unresolvable
  `Cargo.toml` version.

### Dependencies

- comrak 0.50 → 0.51, clap 4.5 → 4.6, libc → 0.2.183, tempfile → 3.27, toml → 1.0.7.
- CI: codecov-action v5 → v6, action-gh-release v2 → v3.

## [0.3.0][] (parity: flowmark-py 0.7.0)

Minor release adopting the Python flowmark v0.7.0 parity surface.
Includes the atomic-aware semantic line wrapping feature and the tag/table block-spacing
rework from upstream.

### Formatter behavior changes

- **Atomic-aware semantic wrapping:** sentence-boundary detection now treats Markdown
  links, code spans, autolinks, and bare URLs as opaque atomic spans.
  A sentence is never broken inside a link (e.g. `[St. John's...](url)` stays whole) and
  a `.` inside an atomic span no longer trips end-of-sentence detection.
  Matches upstream commit `c9bc36f`.
- **Tag block spacing:** HTML-comment-delimited tags wrapping block content (lists,
  tables) now get the same blank-line treatment as Jinja/Markdoc tags.
- **Paragraph → table tight transition:** a table written directly against the preceding
  paragraph (no blank line) now stays tight, matching upstream `#36` ("Wide Table
  Adjacent to Paragraph" fixture).
- **Reference-link normalization:** the issue #45 fix (`[label][]` collapsed form when
  text equals label) is now exact parity with released Python v0.7.0, no longer an
  intentional divergence.

### Parity bug fixes

A real-world corpus sweep (playbook + upstream docs) surfaced edge-case CLI divergences
the curated fixtures missed.
All were fixed (not tolerated) and each is backed by a discriminating test in
`tests/test_known_parity_gaps.rs`, generated from the Python v0.7.0 reference:

- **Block tightness preservation:** adjacent blocks written tight in the source
  (list→list, list→code, code→list, code→code, list→blockquote, code→paragraph,
  paragraph→blockquote) no longer get spurious blank lines inserted; per-item source
  spacing is preserved independently of list looseness.
- **HTML comment with internal blank line:** a comment containing a blank line is no
  longer collapsed onto one line.
- **List/tasklist → thematic break:** the blank line before a `* * *` following a task
  list is preserved.
- **`--plaintext` tag-newline:** adjacent `{% %}` / Markform tags are wrapped instead of
  glued; paired-tag atomic patterns accept a non-letter first char.
- **Inline code-span backtick fence:** code-span fence matching now follows marko’s
  `num_backticks` gating.
- **Malformed GFM table reflow:** ports flowmark’s table-row preservation and separator
  normalization into the wrap path.
- **Reference-image inlining (D19):** `![alt][label]`, `![alt][]`, and shortcut `![alt]`
  now inline to `![alt](url)` (or `![alt](url "title")`) during pre-parse, matching
  Python’s `render_image` which always emits the inline form.
  Previously the COMRAK-WORKAROUND1 PUA marker leaked into the image URL, producing
  literal `\u{F000}HEX\u{F001}` (or the legacy plain-text label) in the rendered output.
  Surfaced by the closed PR #54 reproducer.
- **Badge-pattern reference links:** `[![alt][img]][url]` (image as the text of a
  reference link, the classic GitHub-badge shape) now round-trips cleanly.
  Previously the trailing `[url]` was misread as a shortcut ref due to the inner `]`,
  appending a stray `[]` to the rendered output.
- **Ref-def label lowercasing (D20):** `[Foo]: url` is emitted as `[foo]: url` to match
  Python flowmark’s `render_link_ref_def`, which uses `element.label` (marko normalizes
  ref-def labels to lowercase).
  Definition URL and title are preserved verbatim.

> One escaped-backtick code-span case (`fmr-qmd8`) remains where this port is *more*
> correct than current Python (which strips spaces around later code spans on the same
> line). It is filed upstream as
> [jlevy/flowmark#58]https://github.com/jlevy/flowmark/issues/58 and guarded by
> `gap_e2_escaped_backtick_preserves_spaces`; the port emits the correct,
> space-preserving output rather than shimming the upstream bug.

### Parity

- Python parity baseline updated `0.6.5``0.7.0`.
- 38 new Python tests tracked: 14 mapped (reference links → existing D18; wide table +
  atomic wrapping → `test_ref_docs`), 24 excluded (`test_public_inline_api` — Python
  library API for inline-Markdown introspection with no Rust equivalent yet; the
  CLI/formatter behavior these patterns drive IS covered).
- Cross-binary parity tests now run against `flowmark@0.7.0`.

## [0.2.7][] (parity: flowmark-py 0.6.5)

Parity sync and stabilization release.
Bumps the Python parity baseline from `0.6.4` to `0.6.5` and fixes two formatter parity
gaps found via full-corpus differential testing.

### Formatter behavior changes

- **Thematic-break spacing (D17):** a thematic break (`* * *`, `---`) written tight
  against a neighbouring block now stays tight, matching Python.
  Previously the renderer forced blank lines on both sides.
- **Reference-link normalization (D18):** a reference link whose text equals its
  normalized label now renders as the unambiguous collapsed form `[text][]` instead of
  the fragile shortcut `[text]`; distinct labels render as the full form
  `[text][label]`. This adopts upstream flowmark fix
  [#45]https://github.com/jlevy/flowmark/issues/45 (an intentional, documented
  divergence from released Python `0.6.5`, matching upstream `main`).

### Parity

- Python parity baseline updated `0.6.4``0.6.5` (GFM tilde flanking and CLI-help
  footer needed no Rust change; comrak/Rust already conformant — tests ported).
- 15 new parity tests (D17 ×5, D18 ×10) plus 2 reference-link encoder unit tests.

## [0.2.6][] (parity: flowmark-py 0.6.4)

Release infrastructure and documentation hardening release.
No formatter behavior changes; parity remains pinned to Python flowmark `0.6.4`.

### Improvements

- Stabilized embedded version metadata so release builds produce clean version strings
  (no spurious `-dev.unknown+gunknown` suffix)
- Codified canonical publishing runbook for hybrid Rust/Python multi-channel releases
- Refreshed top-level docs, README, and installation guidance (leading with `uv`/`uvx`)

### CI and release process

- Fixed clippy format-arg lint in version metadata build script
- Added `release_tag` passthrough to `publish.yml` and `pypi.yml` reusable workflows for
  stable version embedding in CI builds
- Moved completed specs from `active` to `done` and reorganized project docs

### Testing

- Retired legacy `cli-golden.tryscript.md` test without coverage loss (replaced by
  focused `file-discovery` and `stdin` tryscript tests)

## [0.2.5][] (parity: flowmark-py 0.6.4)

First multi-channel release: crates.io, PyPI, GitHub Releases, and Homebrew.
No formatter behavior changes; parity remains pinned to Python flowmark `0.6.4`.

### Features

- Added PyPI distribution support for `flowmark-rs` via maturin (`pyproject.toml`) and a
  dedicated GitHub Actions workflow (`pypi.yml`)
- Added incremental cache lifecycle/inspection support in the CLI, including
  `--cache-dir`, `--show-cache`, and `--clear-cache`

### Fixes

- `--clear-cache` no longer requires resolving the current working directory
- Hardened PyPI smoke tests to install only locally built wheels (`--no-index`), and
  restricted PyPI publish job execution to release-triggered runs

## [0.2.4][] (parity: flowmark-py 0.6.4)

Release process and documentation hardening release.
No formatter behavior changes; parity remains pinned to Python flowmark `0.6.4`.

### Improvements

- Clarified and standardized Homebrew installation guidance across README and publishing
  docs
- Tightened CLI help text and usage footer for clearer command-line guidance
- Added and documented README generation sync checks so generated docs stay in lockstep

### CI and release process

- Expanded publishing playbook with explicit release workflow sequencing and
  verification steps
- Added Homebrew tap update workflow details and validation commands
- Applied repository-wide Markdown formatting to keep release/process docs consistent

### Dependencies

- Bumped `clap` to `4.5.60`
- Bumped `tempfile` to `3.26.0`
- Bumped `anyhow` to `1.0.102`

## [0.2.3][] (parity: flowmark-py 0.6.4)

Fixes release workflow for cross-compiled Linux ARM64 binaries.

### Fixes

- Fixed cross-compilation for `aarch64-unknown-linux-musl`: set `CC` env var for `cc-rs`
  to find the cross-compiler
- Release workflow now uses `fail-fast: false` so one target failure doesn’t cancel all
  other builds

## [0.2.2][] (parity: flowmark-py 0.6.4)

Infrastructure release adding pre-built binaries for all major platforms.

### Features

- **Pre-built binaries** for 6 platforms via GitHub Releases: Linux (x86_64, ARM64),
  macOS (x86_64, ARM64), Windows (x86_64, ARM64). Archives include SHA256 checksums.
  `cargo binstall flowmark` now works automatically.
- **Windows CI** added to the test matrix (ubuntu + macOS + Windows)

### Fixes

- Fixed CRLF line-ending handling in golden tests for Windows compatibility
- Tryscript integration tests now correctly skip on Windows (bash-only)

## [0.2.1][] (parity: flowmark-py 0.6.4)

Patch release fixing four formatting parity bugs discovered by corpus-wide comparison
against Python flowmark on 623 real-world files, plus new systematic parity testing
infrastructure.

### Fixes

- **Mixed loose/tight list code fences** (D12b): Code blocks inside loose list items no
  longer get spurious blank lines when the source had none
- **Blockquote blank line indentation** (D13): Blank separator lines inside blockquote
  lists now preserve the full list-content indent (e.g., `"> "`) instead of trimming to
  a bare `">"`
- **Smart quote after inline code** (D15): Apostrophes after code spans are now
  context-sensitive — `config`’s converts to a smart quote while `foo()`'s stays ASCII,
  matching Python’s behavior
- **Empty code blocks** (D16): Empty fenced code blocks no longer produce a spurious
  blank line between the opening and closing fences

### Testing

- New Python-generated golden file parity tests (`tests/parity/corner-cases.md`)
  covering all four fixed bugs across 5 formatting modes
- New cross-binary parity test suite comparing Rust and Python CLI output directly
- Parity verification scripts (`scripts/corpus-parity-check.sh`,
  `scripts/generate-parity-golden.sh`) for corpus-wide regression testing

## [0.2.0][] (parity: flowmark-py 0.6.4)

First formal release.
Complete Rust port with full behavioral parity to Python flowmark v0.6.4.

### Highlights

- **Drop-in replacement** for Python flowmark — identical CLI interface, identical
  formatting output across all modes
- **Single binary, no runtime**`cargo install flowmark`, no Python needed
- **Library crate** — embed formatting in Rust toolchains via `flowmark::FormatOptions`

### Features

- All formatting modes: default (width 88), semantic, auto, plaintext, custom width
- List spacing: preserve, tight, loose
- Typography: smart quotes, ellipsis conversion
- Cleanups: unbold headings
- File discovery: glob patterns, `.gitignore` support, `.flowmarkignore`, config file
  loading (`.flowmark.toml`, `flowmark.toml`, `pyproject.toml`)
- Batch multi-file processing with `--inplace` and `--auto`
- Claude Code skill integration (`--install-skill`, `--skill`)

### Parity Verification

- 430 tests (0 ignored, 0 failures)
- 292 Python tests mapped to Rust equivalents (CI-enforced)
- 15 parity discrepancies identified and resolved
- Golden reference document tested across 4 modes
- 11 tryscript golden tests for end-to-end CLI validation

### Infrastructure

- 12-check CI pipeline (fmt, clippy, test, MSRV, deny, docs, coverage, semver-checks)
- Automated crates.io publishing via trusted publishing (OIDC)
- Cross-platform testing (Ubuntu + macOS)
- Supply chain security via `deny.toml`

## [0.1.3][] - 2025-11-01

Early development release.

[0.1.3]: https://github.com/jlevy/flowmark-rs/releases/tag/v0.1.3
[0.2.0]: https://github.com/jlevy/flowmark-rs/compare/v0.1.3...v0.2.0
[0.2.1]: https://github.com/jlevy/flowmark-rs/compare/v0.2.0...v0.2.1
[0.2.2]: https://github.com/jlevy/flowmark-rs/compare/v0.2.1...v0.2.2
[0.2.3]: https://github.com/jlevy/flowmark-rs/compare/v0.2.2...v0.2.3
[0.2.4]: https://github.com/jlevy/flowmark-rs/compare/v0.2.3...v0.2.4
[0.2.5]: https://github.com/jlevy/flowmark-rs/compare/v0.2.4...v0.2.5
[0.2.6]: https://github.com/jlevy/flowmark-rs/compare/v0.2.5...v0.2.6
[0.2.7]: https://github.com/jlevy/flowmark-rs/compare/v0.2.6...v0.2.7
[0.3.0]: https://github.com/jlevy/flowmark-rs/compare/v0.2.7...v0.3.0
[0.3.1]: https://github.com/jlevy/flowmark-rs/compare/v0.3.0...v0.3.1
[0.3.2]: https://github.com/jlevy/flowmark-rs/compare/v0.3.1...v0.3.2
[0.4.0]: https://github.com/jlevy/flowmark-rs/compare/v0.3.2...v0.4.0
[unreleased]: https://github.com/jlevy/flowmark-rs/compare/v0.4.0...HEAD

<!-- This document follows common-doc-guidelines.md.
See github.com/jlevy/practical-prose and review guidelines before editing.
-->