Release Notes
Features
- (latex)
[latex].verbatim_envs / structure_envs / verbatim_commands in .snapperrc.toml add names to the built-in lists (minted/lstlisting/verbatim, NON_PROSE_ENVS, \\verb/\\lstinline); missing keys keep today's defaults. Extra command names are tokenized like \\verb before split. No regex other: key
- (reflow)
--clause-breaks / clause_breaks with max_width = 0 inserts a newline after every independent-clause mark (,, ;, :, em dash, --) that is already followed by whitespace; a one-clause sentence stays one line; max_width > 0 keeps wrap-prefer-clause. Tokens such as 1,000, URLs, --flags, and unspaced dashes never split. --check uses the same mode
- (cli / check)
--check --output-format json|sarif emits 1-indexed fused / wrap / long diagnostics with excerpts; long is advisory unless --strict-long; stdin --check honors the same JSON/SARIF and exit codes; SARIF URIs are repo-relative (or file: plus invocations[0].workingDirectory)
- (mcp)
mcp is a default feature, so published snapper / snapper-fmt binaries include the stdio server; agents should call it (or the CLI) instead of applying sembr/skills by hand
- (mcp)
format_text accepts clause_breaks, range (start/end, 1-indexed inclusive), and max_width (default 0)
- (mcp)
check_formatting accepts clause_breaks (default false) and returns would_reformat identical to CLI --check, plus the same line diagnostics
- (safety) native parsers record source byte ranges and splice reflowed prose into the original document; Structure/Code/Blank are input slices
- (safety)
format_text runs to a byte fixpoint (cap 4, including A/B cycles) or returns the original; a format-local oracle (region-kind + slice tree; Markdown HTML plus a code-byte check) mismatch also returns the original
- (safety)
format_bytes refuses invalid UTF-8 with InvalidUtf8Error; --use-pandoc refuses (PandocCannotSplice) because the AST has no source offsets
- (safety) property tests and a
cargo fuzz target (fixture corpus) drive format_text per format with the backstops off
- (code-block) comment reflow copies non-comment lines as original slices; only comment spans are rewritten
Bug Fixes
- (latex) render oracle and
--check payloads honor [latex] extras, so a configured \\Verb!%! is not a comment and production backstops no longer revert the file
- (latex) tokenize
\\verb / \\lstinline (optional [...]) before split so inner % is not a comment and inner .!? do not split; unmatched \\verb runs to EOL; unlisted \\begin / \\end are region bounds (optional [...] stays on the begin token); mid-line \\begin{equation} leaves leading words as prose; nested same-name envs (including verbatim/lstlisting) close on matching depth, so \\end{python} inside lstlisting does not steal the closer; listing body scans raw \\begin/\\end (no % stop, no \\verb skip) so print(1) % \\end{lstlisting} and print(\"%\") still close
- (check) fused and long run on the parser prose payload, so
1. Hello world. and See Fig. 1. % TODO cite are not false fused
- (reflow) splice keeps the trailing space before a mid-line TeX
% comment so that line stays one source line
- (reflow)
--max-width keeps links, images, inline code, autolinks, $math$, and Org [[...]] on one line (overlong token may sit alone; no hyphen splits); wrap-created lines that the format grammar would read as a new block (fences, thematic breaks, [ref]:, HTML, >foo, LaTeX %/\begin/\section, RST .., Org |, lists) are backslash-escaped in Markdown or skip-cut (loop) elsewhere; <https://...> / <user@host> are autolinks, not HTML openers, so wrap does not inject \\<; skip-cut wins when a \\ would corrupt an inline token; wrap measures hanging indent and tests interrupt after it; no NBSP; first line of a list item stays a list; second pass does not accumulate backslashes; the render oracle treats wrap-created Markdown escapes (\-, 1\\.) as the source words so splice does not return the original
- (reflow) list continuation sentences hang at the marker width so Org rejoins the item on reparse; Markdown quotes repeat the
> prefix (> One. / > Two., nested > >)
- (latex) a trailing
% eats the newline (TeX nospace join), so foo%\\nbar is no longer emitted as foo% bar (which comments out bar); mid-line % comments leave prose
- (sentence)
w.r.t. is a multi-word abbreviation; \\(...\\) and $$...$$ stay atomic like $...$
- (cli) unknown source extensions (
.rs, .py, no extension) are refused unless --format is explicit; .txt remains plaintext
- (org / sentence) verbatim and inline-code spans pair to the first closer that satisfies Org's border and post rules, so an
= or ~ inside the span no longer orphans the real closer onto the next line
- (sentence) CommonMark
*/** and GFM ~~ pair by flanking rules, so **the end. Still bold** stays one sentence while **complex**. Equity may split after the closer
- (markdown)
> is kept on each reflowed content line (including max_width wraps; prefix counts toward width); two-space and backslash hard breaks are not joined with a space; a quote hard break does not leave a stray > on the next non-quote line; multiline <!-- ... --> is structure (<!-- snapper:off --> still works)
Documentation
- README distinguishes snapper from admk/sembr and sembr/skills; crate keywords include
sembr and markdown
- (markdown / sentence) inline code delimited by two or more backticks can contain a shorter backtick run
- (code-block) a block-comment closer inside a string no longer ends the comment; quote-sequence closers (
""") still match naively so Python docstrings reflow
- (sentence) abbreviation merge does not invent a space before LaTeX
~ (so Eq.~\ref{} stays attached once org ~code~ pairing no longer swallows that tilde)
- (sentence) restore wrapped placeholders from the outside in, so a markdown-link regex match that contains a paired-span token cannot leak
\x00PHn\x00 into output
Notes
- Native
format_text refuses --use-pandoc (PandocCannotSplice); splice needs source offsets the AST does not carry
--clause-breaks with the default max_width = 0 is SemBr rule 5 (break at existing whitespace after clause punct); max_width > 0 still wrap-prefers
- Published binaries include the MCP server (
mcp is a default feature)
Install snapper-fmt 0.10.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/TurtleTech-ehf/snapper/releases/download/v0.10.0/snapper-fmt-installer.sh | sh
Install prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/TurtleTech-ehf/snapper/releases/download/v0.10.0/snapper-fmt-installer.ps1 | iex"
Install prebuilt binaries via Homebrew
brew install TurtleTech-ehf/tap/snapper-fmt
Download snapper-fmt 0.10.0