Skip to content

Releases: nerima-lisp/paredit-cli

v1.7.1

Choose a tag to compare

@github-actions github-actions released this 11 Oct 14:46
32d9dce

New commands

  • inspect dotfiles: inventory .paredit metadata without reading file bodies.
  • inspect test-risk: rank named top-level Common Lisp and Emacs Lisp functions without naming-convention test links by complexity. This is static analysis, not execution coverage.
  • refactor bulk: apply wrap and syntactic rename-in-form operations with non-overlapping replacement spans against an original source snapshot, with validated preview and write modes. Rename does not resolve bindings.
  • refactor unwrap-body: remove a wrapper while retaining arguments from --body-start to the end. Multiple forms require a top-level or recognized sequence position. This is structural editing, not a guarantee of preserved semantics.

Fixes

  • Extend selected control-flow lint rules to Emacs Lisp cl-lib, respecting case-sensitive symbol identities.
  • Support printed Emacs Lisp reader objects as opaque data, preserving their payloads during formatting.
  • Add extract-local-function guards against assignments to free-variable cells.
  • Add multi-path edit kill with overlap rejection, and preflight kill-ring reads.
  • Validate all selected rewrites before the first write in ordinary lint fixes and refactor steps. Batch rollback is not universally guaranteed.
  • Make the package-chain complexity regression control deterministic by counting actual scan work. Existing positive CPU-time bounds remain enforced; the quadratic control must fail both work-growth bounds.
  • Measure the Clojure lint cost budget using thread CPU time on Linux and macOS, excluding scheduler waits. Preserve its generated inputs and 6× threshold; this finite-input budget is not proof of linear complexity.

Compatibility

  • Rust MSRV remains 1.85.
  • Incomplete inspect dotfiles inventories print their report and exit 3, without an enabling flag.
  • Capabilities metadata describes unconditional gates and classifies --intended-bump as a severity gate.
  • Capabilities schema IDs remain unchanged as optional fields are added. Obtain schemas from the same release or binary that produced the document: older cached strict schemas may reject new fields even when schema_version matches.

v1.6.3

Choose a tag to compare

@github-actions github-actions released this 04 Sep 21:02
v1.6.3
e8efd95

A patch release: fixes and text-output changes only. No command, flag, JSON
field, schema_version, exit code, MSRV, or Nix interface changed.

Emacs Lisp formatting

paredit edit format --dialect elisp now agrees with Emacs' own
indent-region in the places it previously did not:

  • Special forms and nested special forms align on the body indentation Emacs
    produces, rather than on the generic call-argument rule.
  • Buffer and conditional forms (with-current-buffer, if, when, and
    friends) indent their bodies at the correct depth.
  • Registered indent properties are honoured even when the target width
    forces a break.
  • Escaped ? symbols survive formatting untouched.
  • A lambda parameter that happens to share a name with a special form stays
    inline instead of being indented as one.
  • Formatting an already-formatted Emacs Lisp file is idempotent.

Expect a diff on the first run over .el files that 1.6.2 formatted.

Blank lines in Emacs Lisp — a changed default

This is the one behaviour change automation should check. With
--max-blank-lines unset, Emacs Lisp now behaves as if --max-blank-lines 1
were given. Unset and 1 are not the same setting: unset discards the source's
blank lines and renders every top-level gap as exactly one, while 1 reads the
source's own count and clamps it.

Blank lines in source Unset (1.6.2, and 1.6.3 outside elisp) 1 (1.6.3 elisp default)
0, between top-level forms 1 — a blank line is inserted 0 — left adjacent
1 or more, inside a body 0 — blank lines are discarded 1 — preserved
2 or more, between top-level forms 1 1
;; source
(defun f ()
  (progn
    1

    2))
(defun g () 3)

1.6.2 deletes the blank line in f's body and inserts one between f and g;
1.6.3 does neither.

Only the default moved — the mechanism is dialect-independent, and
--max-blank-lines 1 produces exactly this output in every other dialect too.
An explicit --max-blank-lines, or format.blank-lines-max-consecutive in
paredit.toml, still wins.

There is no flag that restores 1.6.2's Emacs Lisp output, because the old
behaviour was "unset" and Emacs Lisp can no longer be unset.
--max-blank-lines 0 is the closest and is still not the same: it discards
blank lines, but it does not insert one between adjacent top-level forms.

paredit edit normalize-quotes

--all now rewrites every form the selector matches. The flag was ignored by
this one command, which refused a multi-match selector as ambiguous — and said
so by suggesting the flag that had just been passed.

Given (list (quote x) (quote y)) and
edit normalize-quotes --query '(quote ?x)' --all:

  • 1.6.2 exits 1 with Error [selection.ambiguous]: --query '(quote ?x)' matches 2 forms; pass --all to act on every match, or narrow the selector.
  • 1.6.3 exits 0 and prints (list 'x 'y).

This only turns a previous error into a successful rewrite; no invocation that
succeeded in 1.6.2 changes its output. If a wrapper script catches
selection.ambiguous from this command and retries without --all, that branch
is now unreachable.

paredit edit reindent-defun

A top-level definition that begins at a non-zero column is moved to column zero,
matching what indent-region does to a stray-indented top-level form. This one
is not Emacs Lisp-specific — it applies in every dialect. Spot-check before
running it across a tree that indents some top-level forms deliberately.

paredit inspect capabilities

Exits 0 when the consumer closes stdout early, in every output format:
paredit inspect capabilities --output json | head -5 no longer fails on the
broken pipe. Any || true wrapping this call to swallow that failure can go.

Install

nix run github:nerima-lisp/paredit-cli/v1.6.3 -- --help
nix profile install github:nerima-lisp/paredit-cli/v1.6.3
cargo install --git https://github.com/nerima-lisp/paredit-cli --tag v1.6.3 --locked

v1.6.2

Choose a tag to compare

@github-actions github-actions released this 29 Aug 02:37
v1.6.2
c02264d

What changed

Nothing in the paredit binary. This is a test-infrastructure release.

paredit-feature-lint-fennel-janet-depth's cost gate asserted a wall-clock ratio
(< 1.8) on rules that cost 16–75 ns per check call in a release build. An
assertion at that magnitude is decided by machine load as much as by the code:
the same tree measures 1.05 in a dev build, 1.27 idle in release, and failed a
downstream Nix package build at 2.04 with nothing touched.

The residual growth is not a complexity change either — doubling the file doubles
the tree, so the traversal's working set stops fitting in cache. That is constant
work per call taking more time per call.

The ratio is now an #[ignore]d benchmark. In its place is an assertion on rule
invocation counts, which the head index and the dialect scope fix before any
check body runs and which are therefore identical on an idle machine and a
loaded one. The O(file)-per-call shape the ratio was chasing is still measured
directly by every_rule_stays_far_cheaper_than_a_materializing_one, which has
four orders of magnitude of headroom against its 10× floor.

This matches the pattern already used in lint-introspection, lint-hy-depth,
lint-form-shape, lint-macro-authoring, lint-documentation and
lint-type-declaration.

Do I have to change anything?

No. No command path, flag, exit code, documented JSON field, Nix output, or MSRV
changed.

One case is worth calling out. If you package paredit-cli yourself and run the
test suite during the build — for example rustPlatform.buildRustPackage, whose
doCheck defaults to true — this release removes the test that could fail your
build under sandbox load. Upstream's own packages.default has always skipped
the suite; it belongs to the nextest flake check and nowhere else.

v1.6.1

Choose a tag to compare

@github-actions github-actions released this 26 Aug 09:28
v1.6.1
fff9af9

Patch release. No command, flag, exit code, or JSON field changed since v1.6.0 — the capabilities catalog (paredit inspect capabilities --output json) diffs identical against the tag.

  • Hardened syntax traversal and parallel batch analysis: offset-to-node lookup and child-position lookup are now logarithmic (binary search over ordered children) instead of a linear scan, and batch fix/analysis scheduling now claims work dynamically across threads instead of static contiguous chunks, so one large file no longer pins a worker while the rest sit idle. Internal only — no observable behavior change.
  • Documented paredit editor <file> in the Command model section of the API reference; it was previously only in the README's command table.
  • Checked in a proptest regression seed for remove-unused so that failure stays reproducible in CI.

v1.6.0

Choose a tag to compare

@github-actions github-actions released this 09 Aug 00:24
v1.6.0
f7a09d3

New

  • paredit editor <file> — an interactive, terminal-native text editor for one file: cursor movement, editing actions, and undo/redo, built on the same structural-editing core as the rest of the CLI. (#143)

Fixed

  • paredit edit format/paredit fix now align Common Lisp and Emacs Lisp indentation with real Emacs/SLIME conventions: 36 divergence patterns fixed across the operator table, layout algorithms, and reindenter — including argument alignment under a function call's first argument, an if-branch alignment fix, and coverage for defvar/defstruct/defpackage-family forms. (#144)

Upgrade notes

  • Re-running paredit fix apply or paredit edit format --write on an already-formatted Common Lisp or Emacs Lisp codebase will reformat call sites whose arguments now align differently — review the diff before committing.

v1.5.0

Choose a tag to compare

@github-actions github-actions released this 05 Aug 04:31
v1.5.0
110e4e1

Highlights

  • ~148 new lint rules across Common Lisp, Emacs Lisp, Clojure, Scheme, Racket, Hy, LFE, Carp, Fennel and Janet — condition-system, CLOS-dispatch, data-structure, loop-facility, macro-authoring, declaration, compile-time-phase, and per-dialect idiom rules. Run paredit inspect capabilities --output json to diff the full catalog against v1.4.0.
  • --fix correctness on quoted/reader-prefixed data. Several rules across lint-form-shape, lint-numeric, lint-sequence, lint-control-flow, and the leftover-* family could rewrite hard-quoted data literals, delete a reader prefix (#', ,@, ,., #+, …), or (for leftover-inspect-call/leftover-print-debug) delete real program output as if it were debug residue. If you ran --fix for any of these rules under v1.4.0 or earlier, re-run under v1.5.0 and diff — some of those autofixes were silently corrupting source. See PRs #110, #119–#131, #133–#135, #138–#141.
  • Reader-correctness fixes across nine dialects. Scheme's #-dispatch table (#:keyword, #u8(…), ##name, R6RS #'/#`/#,/#,@, #nil), Racket's dispatch forms, Carp/Hy/Janet/LFE reader macros and string/comma semantics, Emacs Lisp character literals and radix literals, Common Lisp ,. splicing, shebangs, and unused-bracket-as-constituent handling. Files that previously failed to parse (and so received no lint coverage at all) now parse; some previously-accepted files now parse differently — rerun paredit inspect check on your corpus after upgrading. See PRs #91, #96, #98, #100, #103, #104, #106, #108, #113, #115, #126, #132, #136, #140.
  • equality-arity demoted from Error to Warning after a false-positive audit (#121, #123, #125).
  • Nix: aarch64-darwin added alongside x86_64-linux.

Not stable, may still differ

Text output, JSON finding counts (rules were added and sharpened), and edit format layout are explicitly not part of the 1.x compatibility guarantee — see Releases and compatibility.

No breaking changes

Command paths, flags, exit codes, and JSON schema_version are unchanged from v1.4.0.

v1.4.0

Choose a tag to compare

@github-actions github-actions released this 02 Aug 04:59
v1.4.0
9f8db23

Lint

  • 21 new CLOS, condition-system, and iteration lint rules (#81).
  • 8 new REPL-debug-leftover lint rules catching stray print/describe/trace-style debugging calls (#78).
  • Macro hygiene checks are now enforceable (not just report-only) across all eight dialects, with 4 new checks and a scoped auto-fix (#71, #79).
  • lint-custom (defrule) gained dialect scoping, pattern fragments, self-collision detection, and per-rule timing (#68).
  • defrule's pattern language is now unified with the --query/--rewrite engine — the same pattern syntax works in both places (#62).
  • inspect lint --suggest-severity advises when a rule's observed finding density disagrees with its configured severity (#57).
  • Custom-rule parity: --test-rules JSON, --docs, and --explain now cover custom rules the same as built-in ones (#58); loaded custom rules are recognized by lint.enable/disable/deny/warn (#61); config check detects custom-rule severity conflicts (K7).
  • 9 additional lint rules from Section C, part 1 of 13 (#66).

Format

  • paredit.toml gained a project-overridable indent table, width profiles, and quote style (#74).
  • Section J: numeric-case normalization, clause alignment, auto-width, and .editorconfig support (#77).
  • Trailing-comment alignment and blank-line normalization (#69).
  • Fixed width counting, added diff-stat aggregation, and block-comment reindent (#67).

New commands and surfaces

  • inspect data-check and edit kill-ring (Phase 0), with a --format flag and 5 format-specific detectors added to data-check (Phase 1).
  • edit canonicalize normalizes alist/plist data files, refusing files that carry a comment to avoid silently dropping it (K10, plus a follow-up fix).
  • A schema namespace with a defschema DSL (Phase 4); config check rejects duplicate spec keywords and documents :optional.
  • Named checkpoints for refactor state (FR-008).
  • --explain-error and inline ambiguous-selector candidates on refusal (FR-001, FR-002).
  • refactor plan/apply gained a risk aggregate, next-command chaining, a compact headline, and grouped apply output (FR-003–007).
  • PAREDIT_CACHE_DIR and cache.dir config layering (FR-012).

Semantic analysis

  • Type inference widened to Emacs Lisp, Scheme, and Racket (FR-002, FR-003).
  • Effects analysis widened to Emacs Lisp (FR-005); contradiction detection widened to Elisp/Scheme/Racket (FR-007).
  • Value-layer constant folding/propagation widened to Emacs Lisp and fed into effect purity (FR-004, FR-008/A11).
  • Per-dialect --fail-under-dialect coverage thresholds (semantic-coverage).
  • The six semantic introspection commands report per-layer dialect granularity in inspect capabilities (FR-001).
  • defmethod binds its parameters, not its specializers, in the semantic model (FR-009).
  • Read-time-eval handling made cross-dialect (Phase 2).

Agent/MCP experience

  • MCP now surfaces write outcome and carries trimmed tool descriptions (FR-009/FR-010).
  • inspect capabilities reports a per-command writes flag and each command's possible error codes (FR-011).
  • Closed a gap where two newer write commands were missing from MCP's --read-only gate; fixed an apply/diff conflict.

Accessibility

  • Output-wide accessibility: NO_COLOR consistency, verbosity levels, plain-language errors (#54).
  • TERM=dumb/FORCE_COLOR handling, an HTML palette, screen-reader-friendly structure, JSON envelope pinning, and CJK-aware clipping (#60).

Performance

  • Dynamic scheduling, a Node struct size reduction, and a redundant-parse fix for the highest-risk performance/scale items (#76).
  • Lower-risk performance/scale gap fixes across the engine (#64).

Fixes

  • config's custom_conflicts reads now go through the bounded reader.
  • data-check no longer misclassifies typed/racket-flavored files as racket-data.

No commands, flags, or JSON fields were removed or renamed since v1.3.0 — this is a purely additive minor release.

v1.3.0

Choose a tag to compare

@github-actions github-actions released this 30 Jul 05:17
v1.3.0
f646e55

The five shallowest dialects — LFE, Fennel, Janet, Hy, and Carp — gain real
scope analysis, and the dialect capability matrix stops answering "unknown"
for 99% of its cells.

Emacs Lisp gains the same treatment one layer deeper: its own operator
model, a binding table, nine lint rules, and a per-file report.

Added

  • Two error codes. input.dialect-unsupported separates "this file is the
    wrong language for this command" from input.shape-refused, because the two
    call for opposite actions — stop retrying here, versus select a different
    form in this file. argument.flag-combination covers the sixteen places that
    each spelled "these flags do not combine" in their own words.

  • Three new namespaces — query, fix, migrate. The existing three
    split by what a change costs to undo (inspect reads, edit transforms one
    form, refactor plans and applies). These split by what the caller is
    trying to do, over a file set rather than one form. Nothing is removed and
    no older spelling stops working.

    paredit query promotes the --query pattern language out of the selector
    position, where it could only name a form in one named file. query find
    searches a whole workspace and reports each match with its captures, path,
    and stable selector id, through the shared report envelope (so --output sarif, junit, csv and the rest come with it) and with the full input
    surface (--since origin/main, --from-git, --include). query count
    tallies several patterns side by side over one file set, which is what makes
    it a migration's progress bar rather than a number. query replace is the
    one genuinely new capability: a --rewrite template whose ?name
    placeholders are filled with the verbatim source bytes the pattern's
    ?name captured, so a captured 1.0d0 stays a double float and a captured
    string keeps its escapes — nothing is re-serialized, because re-serializing
    is how a rewrite turns 1.0d0 into 1.

    paredit fix is the write side of inspect lint, under a name that says it
    writes. fix apply, fix check, fix plan and fix list were --fix,
    --fix --check, --fix-plan and --list-rules; each builds the arguments
    its old spelling produced and calls the same engine, so the two paths are
    identical by construction rather than by testing. inspect lint also gains
    --fixable, which narrows --list-rules to the rules that carry a fix.

    paredit migrate runs a recipe: an ordered list of query/rewrite steps
    scoped to the dialects the rewrite is correct for. Order matters —
    (if (not p) a nil) becomes (unless p a) only if the negated step runs
    first — and so does scope: (incf x) → (cl-incf x) modernizes Emacs Lisp
    and breaks Common Lisp, where incf is the correct spelling. Two recipes
    ship (elisp-cl-lib, nil-conditionals), as embedded Lisp source parsed by
    the same reader a project's own .paredit/migrations/*.lisp goes through.

    Both writing commands refuse three situations that leave source which still
    parses and is still wrong, and which the reparse guard therefore cannot
    catch: a match nested inside one already rewritten, a rewrite that would
    delete a comment no capture carries over, and a match inside quoted data —
    '(a (if x y nil) b) is a list literal, and rewriting it changes the
    program's data rather than its code. All three are counted in every output
    format, including as zeroes; --allow-comment-loss and --include-quoted
    override the two that are overridable.

    query find/query count/query replace and migrate run survive a file
    that will not parse: it is reported on stderr and excluded, and the files
    that did parse still get a report. Only a run where every file fails is a
    hard error. migrate run also skips a step before parsing it when none of
    the step's pattern's literal atoms appear in the file's current text — a
    sound, one-directional prefilter that avoids materializing a syntax tree
    for a step that cannot match.

  • Five clone-detection commands built on the existing tree-edit-distance
    scorer, which similarity and duplicates had been under-using.
    inspect clone-classes groups near-duplicate forms into classes, labels each
    on the standard Type-1/2/3 taxonomy, and ranks them by the lines extracting
    one would save — five copies of a helper are one class to act on rather than
    ten pairs to read. inspect clone-sequences finds duplicated runs of adjacent
    sibling forms, the sub-form clones that no whole-form report can see because
    the duplication does not line up with a form boundary. inspect clone-external
    compares the project against a reference corpus across head symbols, so a
    local join-strings matches a library str:join. inspect clone-threshold
    recommends a --threshold from the project's own similarity distribution,
    reporting the histogram, an Otsu split, the widest distribution gap and
    percentiles so the recommendation can be judged rather than taken.
    inspect clone-genealogy orders each class by the commit that introduced its
    members, separating the original from the copies.

  • inspect similarity labels every reported pair with its clone type, the
    number of atoms renamed, and whether that renaming is a bijection. An
    inconsistent Type-2 renaming is the shape a copy-paste bug takes when one
    occurrence of a variable was missed.

  • Six new ways to choose which files get analysed. Discovery was a
    directory walk with four booleans and an exact-path exclude list. It could
    not be told to respect .gitignore, to take the file set from a build
    definition, or to look only at what a pull request changed. The flags now
    divide into selectors, which decide where the candidate list comes from,
    and filters, which narrow whatever the selector produced:

    • --since <git-ref> — only the files that differ from a ref, which is the
      single biggest lever on CI time. Deleted files drop out, a rename reports
      its destination, and an unresolvable ref is an error rather than an empty
      change set that would let a gate pass without examining anything.
    • --from-git — the file set from git ls-files.
    • --from-manifest — the project's own build definition: ASDF defsystem
      :components (through :module and :pathname, in declaration order),
      deps.edn, shadow-cljs.edn, project.clj, and an Emacs Lisp
      Package-Requires header. A named component missing from disk is reported
      rather than silently dropped.
    • --paths-from <FILE|-> — a list you computed yourself, newline- or
      NUL-separated.
    • --from-archive <ARCHIVE|-> --extract-to <DIR> — an uncompressed tar, from
      a file or stdin. Compression and transport stay with the shell, which keeps
      an HTTP client, a TLS stack and a decompressor out of a tool trusted with
      your source tree. Extraction refuses rather than sanitises absolute paths,
      .. components, symlinks, hardlinks, devices, and overwriting.
    • --cache-dir <DIR> — reuse a previous scan. Keyed on everything that can
      change the selection and validated against the tree, so a stale entry is a
      miss rather than a wrong answer.
  • Filters: --include / --exclude-glob in gitignore(5) syntax,
    .gitignore and .pareditignore honoured by default (--no-gitignore,
    --no-pareditignore, --no-ignore, and three PAREDIT_NO_* environment
    variables), and --follow-symlinks. Ignore precedence follows git exactly:
    the deeper file wins, the last matching pattern within a file wins, a
    repository boundary cuts the stack, and a root named on the command line is
    never ignore-filtered.

  • paredit inspect sources. Runs selection and stops, reporting which rule
    dropped every file that did not make it. It parses nothing, which makes it
    the cheapest way to answer "did that CI run find nothing, or did my pattern
    find nothing".

  • Discovery reports repository boundaries, so a run over several checkouts
    groups its result per repository instead of flattening them.

  • Six new ways to select a form. --path and --at were the only two,
    and both cost a round trip to build: an agent had to run inspect outline,
    read a path out of it, and hope nothing moved in between. Every command that
    takes a target now also accepts:

    • --query '(defun ?name ...)' — an S-expression pattern, written in the
      file's own dialect and read with its own reader. _ matches one form,
      ?name binds one, ... matches a run, ?body... binds a run. Captures
      may be constrained (?x:list, ?x:number, …), a repeated name is a
      back-reference ((eq ?x ?x) finds self-comparisons), and --capture name
      selects the bound sub-form rather than the whole match.
    • --name <symbol> — the definition of that name, at any nesting depth.
    • --line-column LINE[:COLUMN] — a 1-based editor coordinate, columns
      counted in characters. The column defaults to 1.
    • --id <id> — a content-addressed id that keeps naming the same form after
      edits elsewhere in the file, where a --path would not.
    • --from / --to — a contiguous range of siblings, each end given as a
      compact selector (0.2, at:120, name:foo, query:(defun ?n ...)).
    • --parent / --child N / --sibling ±N — relative moves over any of the
      above, applied up-across-down.
    • --select <selector> — any of the above in one flag, using the compact
      grammar. This is how the richer selectors reach the eight commands whose
      own flags already claim these names (refactor introduce-let --name is
      the new binding's name, rename-binding --from/--to are symbols):
      introduce-let, inline-let, remove-unused-binding,
      thread-expression, unthread-expression, unwrap-call,
      extract-function, extract-constant now take --path, --at, and
      --select. They also report the resolved path in their JSON plan whe...
Read more

v1.2.1

Choose a tag to compare

@github-actions github-actions released this 28 Jul 07:31
v1.2.1
4bd610e

No command, flag, exit code, or JSON field changed in this release: nothing
under src/ or packages/ was touched. What changed is how the project
verifies itself. The whole verification gate went from 27 minutes to 8, by
removing work it was doing twice rather than by checking less.

Changed

  • The Rust flake checks are crane
    derivations sharing pre-built dependency artifacts, instead of four
    independent buildRustPackage derivations that each recompiled the whole
    dependency graph. Consumers of packages.default or overlays.default get
    the same binary; what changed is that building it no longer runs the test
    suite, which now belongs to checks.<system>.nextest alone. flake.nix
    gains a crane input, and lib.<system>.ciCheckNames is exported so CI can
    derive its job matrix from the checks rather than restating them.
  • checks.<system>.msrv stops at cargo check --all-targets instead of
    building and testing the whole workspace under the MSRV toolchain. It
    verifies the same thing — that the declared MSRV still compiles the
    workspace — without a third full run of the test suite.
  • The dev profile emits line tables instead of full debug information.
    Backtraces still resolve every frame to file:line; use
    RUSTFLAGS=-Cdebuginfo=2 when step-debugging.
  • CI runs one job per flake check instead of one nix flake check for all of
    them, and derives that job list from the flake so a new check cannot silently
    stop being verified.

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 28 Jul 07:14
v1.2.0
afa68b6

No command, flag, exit code, or JSON field changed in this release: the
capability catalogue is byte-identical to v1.1.0. What changed is the shape of
the source tree behind it, and — for anyone using the crate as a library — the
type of every error it can return.

Changed

  • The single crate is now a 24-package Cargo workspace: six paredit-core-*
    packages (syntax, semantics, edit, lint-engine, workspace, cli) and eighteen
    paredit-feature-* packages, with the binary as a thin composition root.
    The paredit_cli façade re-exports every package module, so existing import
    paths such as paredit_cli::domain::sexpr still resolve.
  • The lint engine no longer depends on the rule registry. Rules are supplied
    through a RuleCatalog, which breaks the cycle that previously forced the
    engine and the 134 rules into one compilation unit.
  • Breaking for library consumers. Fallible entry points return typed error
    enums instead of anyhow::Result — SexprError, EditRefusal, LintError,
    CliError, and per-feature equivalents. A caller that names a return type or
    matches on an error must be updated; ? into an anyhow::Result still
    compiles unchanged. The CLI's own stderr text is unchanged, because main
    still converts at the boundary.
  • Several types now make invalid states unrepresentable rather than checking
    for them: inline-function's call selection, split-file's destination, and the
    refactor manifest's hash and flag comparisons, which are derived rather than
    stored.
  • The compatibility guide no longer lists the Rust library API as a stable
    surface. The crate is publish = false with no registry release, and the
    supported interface is the command line — see
    Releases and compatibility.