paredit-cli
paredit-cli is a command-line tool for inspecting, editing, and safely
refactoring Lisp source. It parses first, edits only balanced S-expression
structure or exact atom tokens, and validates the result — so symbol-oriented
rewrites never touch strings or comments.
It supports Common Lisp, Emacs Lisp, Scheme, Clojure, Janet, and Fennel sources, and is designed for both people and AI coding agents.
Quick start
paredit inspect check --file source.lisp
paredit edit format --file source.lisp
paredit refactor rename-symbol --file source.lisp --from old-name --to new-name
The CLI has exactly three source-facing namespaces:
paredit inspect: read-only reports and analysis.paredit edit: structural edits of a selected form — stdout by default,--difffor a unified diff,--writeto update the file in place.paredit refactor: planned semantic changes with preview and verification workflows — see the refactor workflow.
There are no legacy top-level command aliases; the only meta command is
paredit completions <shell>. Forms are addressed with tree paths or byte
offsets — see Selecting forms. Automation and AI coding
agents should start with the agent interface, including
paredit inspect capabilities for one-call discovery of the whole command
surface.
Install
nix run github:takeokunn/paredit-cli -- inspect check --file source.lisp
See Installation for Nix profiles, the flake overlay,
Cachix binary caches, and cargo install. Contributors should start with
Development.
Installation
paredit ships as a single binary. Nix is the primary distribution channel;
Cargo works anywhere a Rust toolchain is available.
Run without installing (Nix)
nix run github:takeokunn/paredit-cli -- inspect check --file source.lisp
The companion lint and format gates are exposed as flake apps:
nix run github:takeokunn/paredit-cli#lint -- .
nix run github:takeokunn/paredit-cli#format -- --check .
Install into a Nix profile
nix profile install github:takeokunn/paredit-cli
Prebuilt binaries are published to the public
takeokunn-paredit-cli Cachix
cache, so neither command has to compile the crate from source:
cachix use takeokunn-paredit-cli
Use as a flake input
Add the flake and pick the packages or the overlay:
{
inputs.paredit-cli.url = "github:takeokunn/paredit-cli";
outputs = { nixpkgs, paredit-cli, ... }: {
# Directly as a package:
# paredit-cli.packages.${system}.default
# Or through the overlay, which provides pkgs.paredit-cli,
# pkgs.paredit-lint, pkgs.paredit-format, and pkgs.paredit-format-files:
# nixpkgs.overlays = [ paredit-cli.overlays.default ];
};
}
The flake also exports lib.${system}.mkLintCheck, mkFormatCheck, and
treefmtFormatter for wiring structural checks into another project’s
nix flake check — see Integrations.
Install with Cargo
cargo install --git https://github.com/takeokunn/paredit-cli --locked
The minimum supported Rust version is 1.85 (edition 2024).
Pin automation
The examples above follow the latest default branch. For CI, production
automation, or a reproducible developer environment, pin an immutable commit
instead. Replace <commit> with a full commit SHA that you have reviewed:
nix run github:takeokunn/paredit-cli/<commit> -- --help
nix profile install github:takeokunn/paredit-cli/<commit>
cargo install --git https://github.com/takeokunn/paredit-cli --rev <commit> --locked
When upgrading a pin, inspect the release notes and compare the machine-readable command catalog before accepting the new revision:
paredit inspect capabilities --output json
Verify
paredit --help
paredit inspect --help
Command model
Every source-facing command belongs to one of three namespaces. This gives automation a stable first decision: inspect, edit, or refactor.
paredit inspectreads and reports without writing.paredit edittransforms one selected form; stdout by default,--difffor a unified diff,--writeto update the file in place.paredit refactorplans, previews, verifies, and applies semantic changes.
The only command outside these namespaces is the paredit completions <shell> meta command, which prints shell completion scripts for bash, zsh,
fish, elvish, and powershell.
Run paredit <namespace> --help for the authoritative list on your installed
version, and paredit <namespace> <command> --help for each command’s
contract, arguments, and output formats. For a machine-readable catalog of
the entire surface in one call, run:
paredit inspect capabilities --output json
Inspect
paredit inspect never writes source files. Prefer these commands for
discovery, impact analysis, and preflight checks.
| Command | Purpose |
|---|---|
check | Validate that input is a balanced S-expression document. |
dialect | Detect Lisp dialect from --file extension or explicit --dialect. |
stats | Print parse, dialect, and structural metrics for agent planning. |
agent-report | Print a complete JSON report for AI coding agent refactor planning. |
capabilities | Print a machine-readable catalog of every command, flag, default, and enum value. |
outline | Print top-level forms with paths, spans, and definition hints. |
form | Report one selected form with local structure for refactor planning. |
find-symbol | Find exact atom occurrences without touching strings or comments. |
symbols | Report exact atom occurrences across explicit files for rename planning. |
calls | Report list-head call sites across explicit files for arity refactor planning. |
signature | Compare callable definitions and call-site arity across explicit files. |
call-graph | Report internal and optional external call graph edges. |
impact | Report refactoring impact risks for one symbol across explicit files. |
workspace | Discover Lisp sources under roots and report parse/refactor inventory. |
dependencies | Report package, system, load, and qualified-symbol dependencies. |
packages | Report Common Lisp package declarations across explicit files. |
definitions | Report definition-like top-level forms across explicit files. |
unused-definitions | Report definitions with no external exact atom references. |
duplicates | Report repeated structural S-expression shapes across explicit files. |
similarity | Report structurally similar S-expression forms across explicit files. |
lets | Report local let bindings and inline safety for refactor planning. |
Most reports accept --output json for machine-readable results.
Edit
paredit edit makes one structural transformation on the form selected by
--path or --at (see Selecting forms). By default the
rewritten document is printed to standard output and the file is untouched.
Mutating commands also accept:
--diff— print a unified diff against the input instead of the whole rewritten document.--write— persist the result back to--file. The write is refused if the rewritten document no longer parses, and file writes are staged with automatic rollback.
| Command | Purpose |
|---|---|
format | Print a canonical, indentation-based rendering. |
repair-unclosed-lists | Append matching delimiters for parser-detected unclosed lists; refuse all other parse errors. |
select | Print the S-expression selected by --path or --at. |
replace | Replace the selected S-expression with replacement text. |
kill | Remove the selected S-expression. |
wrap | Wrap the selected S-expression in a new list. |
splice | Remove one list pair while keeping its children. |
raise | Replace the selected expression’s parent list with the selection. |
transpose-forward | Exchange the selected expression with its next sibling while keeping trivia in place. |
transpose-backward | Exchange the selected expression with its previous sibling while keeping trivia in place. |
slurp-forward | Pull the next sibling into the selected list. |
slurp-backward | Pull the previous sibling into the selected list. |
barf-forward | Push the last child out of the selected list. |
barf-backward | Push the first child out of the selected list. |
For example, preview then apply a wrap of the third child of the first top-level form:
paredit edit wrap --file source.lisp --path 0.2 --diff
paredit edit wrap --file source.lisp --path 0.2 --write
Refactor
paredit refactor contains the reviewable workflow commands and the semantic
refactorings they gate. See Refactor workflow for the
plan/preview/verify/apply lifecycle.
Workflow commands
| Command | Purpose |
|---|---|
plan | Produce an ordered, gated refactoring plan for AI coding agents. |
verify | Verify pre/post refactoring invariants for agents and CI gates. |
preview | Preview exact refactoring rewrites without modifying files. |
check | Validate a refactor preview manifest without writing files. |
status | Summarize a preview manifest into agent-safe next actions. |
apply | Apply a previously generated preview manifest with hash guards. |
diff | Render a verified diff from a preview manifest without writing files. |
workspace-plan | Discover Lisp sources under roots and build a gated refactor plan. |
workspace-preview | Discover sources and preview exact refactoring rewrites. |
workspace-execute | Execute a workspace refactor with preview gates and post-write verification. |
Definition and file layout
| Command | Purpose |
|---|---|
remove-definition | Plan or remove a top-level definition from one file. |
remove-unused-definitions | Plan or remove unused top-level definitions across files. |
move-definition | Plan or move a top-level definition between files. |
split-file | Plan or split multiple top-level definitions into another file. |
sort-definitions | Plan or sort contiguous top-level definition blocks in one file. |
move-form | Plan or move any top-level form between files. |
insert-top-level | Insert exactly one top-level S-expression before, after, or at the end of a file. |
replacement-plan | Convert duplicate groups into reviewed replace-forms batches. |
replace-forms | Plan or replace multiple reviewed forms in one file. |
Packages
| Command | Purpose |
|---|---|
add-export | Plan or add a symbol to a Common Lisp defpackage :export option. |
sort-package-exports | Plan or sort defpackage :export symbol designators. |
sort-package-options | Plan or sort defpackage option forms. |
merge-package-options | Plan or merge duplicate defpackage option forms. |
rename-package | Plan or rename package designators and qualified prefixes. |
Renames
| Command | Purpose |
|---|---|
rename-at | Rename whatever symbol occupies a byte offset, dispatching to the owning namespace and scope. |
rename-symbol | Rename exact atom occurrences without touching strings or comments. |
rename-in-form | Rename exact atom occurrences inside one selected form. |
rename-binding | Rename one local binding and only the references in its lexical scope. |
rename-symbols | Plan or apply an exact atom rename across explicit files. |
rename-function | Plan or apply a Common Lisp callable definition and designator rename. |
rename-macrolet | Plan or apply a macrolet/compiler-macrolet binding and call-site rename. |
rename-symbol-macro | Plan or apply a define-symbol-macro binding and value-reference rename. |
rename-local-function | Plan or apply a flet/labels local function binding and call-site rename. |
Calls and functions
| Command | Purpose |
|---|---|
replace-function-calls | Plan or replace callable call-site heads across explicit files. |
wrap-function-calls | Plan or wrap callable call sites in another function or macro call. |
unwrap-function-calls | Plan or remove a unary wrapper around callable call sites. |
unwrap-call | Replace one selected wrapper call with one selected argument. |
thread-expression | Convert a nested call chain into a thread-first or thread-last pipeline. |
unthread-expression | Convert a threading pipeline back into nested calls. |
extract-function | Extract the selected expression into a top-level function with inferred parameters. |
extract-local-function | Extract the selected expression into a Common Lisp flet or labels binding. |
extract-constant | Extract the selected expression into a top-level constant. |
inline-function | Inline one selected function call using a selected function definition. |
inline-lambda | Replace a safe, immediately invoked Common Lisp lambda with a parallel let. |
inline-local-function | Inline the sole direct call in a safe, single-binding Common Lisp flet form. |
inline-symbol-macro | Expand a conservative single-binding Common Lisp symbol-macrolet form. |
inline-literal-constant | Inline an immutable self-evaluating Common Lisp defconstant value. |
convert-labels-to-flet | Convert a non-recursive Common Lisp labels form into flet. |
convert-flet-to-labels | Convert a Common Lisp flet form into labels when definition references cannot be captured. |
rename-block | Rename a selected Common Lisp block and matching return-from references. |
rename-tag | Rename one tag in a selected Common Lisp tagbody and matching go references. |
remove-unused-block | Remove a selected Common Lisp block with no matching return-from. |
remove-unused-tag | Remove an unreferenced tag from a selected Common Lisp tagbody. |
Parameters and bindings
| Command | Purpose |
|---|---|
add-function-parameter | Add a parameter to a selected function and explicit call sites. |
move-function-parameter | Move one positional parameter in a function and its call sites. |
swap-function-parameters | Swap two positional parameters in a function and its call sites. |
reorder-function-parameters | Reorder all positional parameters in a function and its call sites. |
remove-function-parameter | Remove one positional parameter from a function and its call sites. |
introduce-let | Replace the selected expression with a local binding in the enclosing list. |
inline-let | Inline a single local let binding into its body. |
convert-let-to-let-star | Convert a Common Lisp or Emacs Lisp let to let* when later initializers do not reference earlier bindings. |
convert-let-star-to-let | Convert a Common Lisp let* to let when later initializers do not reference earlier bindings. |
convert-do-star-to-do | Convert a Common Lisp do* to do when later initializers and step expressions do not reference earlier bindings. |
convert-prog-star-to-prog | Convert a Common Lisp prog* to prog when later initializers do not reference earlier bindings. |
merge-nested-let-star | Merge a directly nested Common Lisp or Emacs Lisp let* into one sequential binding form. |
split-let-star | Split a Common Lisp or Emacs Lisp let* into nested sequential binding forms at --binding-index. |
merge-nested-let | Merge directly nested Common Lisp or Emacs Lisp parallel let forms when inner initializers are independent. |
merge-nested-flet | Merge directly nested Common Lisp flet forms when inner definitions do not reference outer local functions. |
split-let | Split a Common Lisp or Emacs Lisp parallel let at --binding-index without capturing initializer references. |
eliminate-empty-binding-form | Remove an empty Common Lisp or Emacs Lisp let or let* from a known expression position. |
flatten-progn | Flatten directly nested Common Lisp or Emacs Lisp progn forms in a safe expression context. |
convert-if-to-cond | Convert a Common Lisp or Emacs Lisp (if test then [else]) form to cond. |
convert-cond-to-if | Convert simple Common Lisp or Emacs Lisp cond clauses to nested if forms. |
convert-when-to-if | Convert a Common Lisp or Emacs Lisp when form to if. |
convert-unless-to-if | Convert a Common Lisp or Emacs Lisp unless form to if. |
convert-if-to-when | Convert a Common Lisp or Emacs Lisp if without a meaningful else to when. |
convert-if-to-unless | Convert a Common Lisp or Emacs Lisp if with a literal nil then branch to unless. |
remove-unused-binding | Plan or remove one unused local let binding. |
Selecting forms
Most paredit edit and several paredit refactor commands operate on one
selected expression. There are two selectors, and every report command that
prints locations emits values you can pass straight back in.
Tree paths: --path
A path is a dot-separated list of zero-based child indexes, starting at the top level of the document. Given:
(defun foo (x) ; top-level form 0
(+ x 1))
(defvar *limit* 10) ; top-level form 1
--path 0selects the wholedefun.--path 0.0selects the atomdefun.--path 0.2selects the parameter list(x).--path 0.3selects the body form(+ x 1).--path 1.2selects10.
Paths count every child expression, including the head atom. Comments and whitespace are not children, so paths stay stable under reformatting.
Use --path when scripting deterministic edits: the same document always
yields the same path.
Byte offsets: --at
--at <offset> selects the smallest expression containing the given byte
offset. Use it when another tool — a grep hit, a compiler message column, or
a previous paredit report — already gives you a byte position:
paredit edit select --file source.lisp --at 42
--path and --at are mutually exclusive; pass exactly one.
Getting paths and spans from reports
You never need to count parentheses by hand. These commands print paths and byte spans for everything they report:
# Top-level forms with paths, spans, and definition hints.
paredit inspect outline --file source.lisp --output json
# One form with its local structure (children, paths, spans).
paredit inspect form --file source.lisp --path 0 --include-source --output json
# Exact atom occurrences with spans, ready for --at.
paredit inspect find-symbol --file source.lisp --symbol foo --output json
# Everything at once, for agent planning.
paredit inspect agent-report --file source.lisp
A typical loop: run outline to find the top-level form, run form on that
path to see its children, then pass the child path to the edit or refactor
command.
Files and stdin
Single-document commands read --file when given and stdin otherwise.
Dialect detection uses the file extension (.lisp, .asd, .el, .scm,
.clj, .cljc, .cljs, .janet, .fnl); pass --dialect explicitly for
stdin input or unusual extensions where the command accepts it.
Report commands that take multiple files (symbols, calls, signature,
…) require explicit file arguments, while workspace and the
refactor workspace-* commands discover sources under directory roots.
Refactor workflow
paredit refactor separates deciding, previewing, writing, and verifying so
that automation can stop at any gate. The full lifecycle for a cross-file
rename looks like this:
# 1. Plan: gates, risk summary, and an ordered step list.
paredit refactor plan --symbol old-name src/core.lisp src/util.lisp
# 2. Preview: exact rewrites, no files touched. --manifest-out writes the
# manifest file and prints its hash, so no extra `status` call is needed.
paredit refactor preview --from old-name --to new-name \
--manifest-out preview.json src/core.lisp src/util.lisp
# 3. Review the manifest without writing.
paredit refactor check --manifest preview.json
paredit refactor status --manifest preview.json
paredit refactor diff --manifest preview.json
# 4. Apply the reviewed manifest with hash guards (hash printed by step 2).
paredit refactor apply --manifest preview.json \
--expect-manifest-hash "$HASH" --write
# 5. Verify post-conditions.
paredit refactor verify --symbol old-name --new-symbol new-name \
--phase post src/core.lisp src/util.lisp
Plan output is a contract
paredit refactor plan emits JSON with a decision block
(status, next_action, safe_to_automate), gates with
blocks_automation flags, a risk_summary, and an ordered steps array
whose entries carry runnable command strings. An agent can execute the plan
literally: run each step in order and stop when a gate blocks automation or a
step exits non-zero.
Policy flags such as --require-definitions, --require-references, and
--fail-on-blocking-gate turn advisory checks into hard failures, which makes
plan usable as a CI gate on its own.
Preview manifests and hash guards
preview and workspace-preview print a manifest describing every byte-exact
edit. apply refuses to write when:
- the manifest hash does not match
--expect-manifest-hash; - a target file changed on disk since the preview was generated; or
- any rewritten output no longer parses.
This means a manifest can be produced in one CI job, reviewed as an artifact, and applied in a separate controlled job without trusting the intermediate steps.
Direct refactorings still gate writes
Named refactorings such as rename-function, extract-function, or
add-function-parameter run in plan mode by default and only modify files
when --write is passed after their own validation gates pass. Symbol-oriented
rewrites never touch strings or comments, and Common Lisp scope-aware
refactors preserve flet/labels/macrolet/symbol-macrolet binding
boundaries.
Workspace scope
For repository-wide changes, start with discovery and stay inside the same lifecycle:
paredit inspect workspace --output json .
paredit refactor workspace-plan --symbol old-name .
paredit refactor workspace-preview --from old-name --to new-name . > preview.json
paredit refactor workspace-execute --from old-name --to new-name --write .
workspace-execute wraps preview gates and post-write verification into one
command for cases where a human already reviewed the plan.
Safety reference
paredit-cli keeps inspection, source edits, and semantic refactorings separate so automated clients can choose an appropriate review path.
Inspect is read-only
All paredit inspect commands report information without modifying source files. Prefer these commands for discovery, impact analysis, and preflight checks.
Edit previews before it writes
paredit edit commands return transformed source on standard output by default and never touch the file. Preview the change as a diff, then apply it in place with --write:
paredit edit format --file source.lisp --diff
paredit edit format --file source.lisp --write
--write refuses to persist a result that no longer parses, and writes are staged with automatic rollback, so a failed write cannot leave a truncated or unbalanced file behind.
Refactor is explicit
Use paredit refactor plan, paredit refactor preview, and paredit refactor verify before paredit refactor apply when the workflow is available. These commands make planned changes and verification results visible before a write is requested.
Workspace scope
For workspace operations, start with paredit inspect workspace to identify the affected files. Use the workspace planning and preview commands before paredit refactor workspace-execute.
Automation guidance
- Discover with
paredit inspect. - Review an
editresult (--diffor stdout) before passing--write. - Plan, preview, and verify a
refactorbefore applying it. - Treat non-zero exits and validation failures as blockers.
See the agent interface for exit codes, the JSON output contract, and a complete safe editing loop.
Agent interface
paredit is designed to be driven by AI coding agents and other automation.
This page collects the machine-facing contracts in one place.
Discover the command surface
One call returns a catalog of every command, flag, default, and enum value, generated from the same definition that parses the arguments — it cannot drift from the real interface:
paredit inspect capabilities --output json
paredit inspect capabilities --output text # compact human-readable listing
The JSON shape is a tree: the root lists top-level commands (the
inspect/edit/refactor namespaces plus the completions meta command),
each with nested commands and an args array. Every arg entry carries
long, short, kind (option, flag, or positional), help,
required, repeatable, default_values, and possible_values.
Exit codes
| Code | Meaning |
|---|---|
0 | Success. For plan/preview commands: the report was produced and no requested gate failed. |
1 | Operational failure: parse errors, missing targets, refused writes. |
2 | Usage error: unknown command, unknown flag, or invalid value (from argument parsing). |
3 | Policy gate failure: a requested --fail-on-* / --require-* gate tripped after the report was printed. The invocation itself was valid — read the report and decide. |
Treat any non-zero exit as a blocker, but branch on the code: 3 means “the
tool worked and told you no”, 1 means the invocation itself broke. Policy
gates exist so a command exits non-zero instead of silently under-matching;
prefer running plan/preview commands
with explicit gates such as --fail-on-blocking-gate, --require-edits 1,
or --require-definitions 1. Occurrence reports gate the same way:
inspect find-symbol/inspect symbols take --require-occurrences N and
inspect calls takes --require-calls N, so an expected-but-missing symbol
fails loudly instead of returning an empty report. Every rename-* command
accepts --fail-on-no-change, which turns a zero-match rename from a silent
no-op into an exit-1 failure — pass it whenever you expect the rename to do
something.
Output contract
--output jsonis the stable, parseable contract; prefer it everywhere it is offered. Text output is for humans and may change freely.- Every object-shaped JSON report carries a top-level
schema_version(currently1). New fields may be added within a version; renames or removals bump it. (inspect outlineemits a bare array and is the one exception.) - JSON reports go to stdout; diagnostics and errors go to stderr as text.
paredit editcommands print the whole rewritten document to stdout by default.--diffswitches stdout to a unified diff;--writepersists the result to--fileinstead and prints nothing (combine with--diffto write and see the diff at once).
Safe editing loop
The recommended loop for one file:
# 1. Validate before touching anything.
paredit inspect check --file source.lisp
# 2. Locate the target form (paths and spans — see Selecting forms).
paredit inspect outline --file source.lisp --output json
# 3. Preview the structural edit as a diff.
paredit edit wrap --file source.lisp --path 0.2 --diff
# 4. Apply it in place. The write is validated and rolled back on failure.
paredit edit wrap --file source.lisp --path 0.2 --write
# 5. Validate again.
paredit inspect check --file source.lisp
--write refuses to write when the rewritten document no longer parses, and
file writes are staged with automatic rollback, so a failed write never
leaves a truncated or unbalanced file behind.
For semantic, multi-file changes use the gated
refactor workflow: plan → preview → verify --phase pre
→ --write (or manifest apply with hash guards) → verify --phase post.
Rules of thumb for agents
- Never hand-edit balanced delimiters; every structural change goes through a paredit command.
- Run
paredit inspect checkbefore and after a batch of edits. - Never pass
--writeuntil a no-write preview (--diff, plan JSON, or preview manifest) has been reviewed. - Use the narrowest command that matches the binding kind:
rename-function,rename-binding,rename-macrolet, … before falling back to the genericrename-symbol. - Prefer
--pathfrom a report over--atguesses; reserve--atfor offsets sourced from another tool.
The repository also ships this contract as an agent skill in
skills/paredit-cli/SKILL.md, ready to drop into a Claude Code or similar
agent configuration.
Integrations
GitHub Actions
The repository ships a composite action that runs the structural lint and canonical-format gates:
- uses: takeokunn/paredit-cli@main
with:
mode: lint # lint | format | fix
paths: src tests # files or directories, scanned recursively
| Input | Default | Meaning |
|---|---|---|
mode | lint | lint fails on structural parse errors; format fails when a source is not in canonical format; fix rewrites sources in place. |
paths | . | Space-separated files or directories to scan. |
version | pinned ref | paredit-cli git ref to run; defaults to the ref the action is pinned to. |
cachix-name | takeokunn-paredit-cli | Public Cachix cache for prebuilt binaries. |
For ad-hoc use, invoke the Nix flake directly with canonical command paths:
- name: Check Lisp source
run: nix run github:takeokunn/paredit-cli -- inspect check --file source.lisp
Nix flake
The flake exposes packages, apps, and reusable check helpers:
nix run github:takeokunn/paredit-cli -- inspect check --file source.lisp
nix run github:takeokunn/paredit-cli#lint -- .
nix run github:takeokunn/paredit-cli#format -- --check .
Downstream flakes can reuse the gates and the formatter:
lib.<system>.mkLintCheck { src = ./.; }— a derivation that fails on structural parse errors, suitable forchecks.lib.<system>.mkFormatCheck { src = ./.; }— the canonical-format gate as a derivation.lib.<system>.treefmtFormatter— a treefmt formatter entry covering.lisp,.asd,.el,.scm,.clj,.cljc,.cljs,.janet, and.fnlsources.overlays.default— addsparedit-cli,paredit-lint,paredit-format, andparedit-format-filesto nixpkgs.
Nix development shell
nix develop
cargo test
paredit inspect check --file source.lisp
AI coding agents
The skills/paredit-cli/ directory packages the agent-facing skill contract:
when to reach for paredit instead of hand-editing delimiters, and which
plan/preview/verify sequences are safe to automate.
GitHub Pages
This site is built from docs/src with mdBook via the Nix flake
(nix build .#docs) and published by the Publish documentation workflow.
The same derivation runs as checks.documentation in nix flake check, so a
broken book fails CI before it can reach the site.
Development
Everything a contributor needs is provided by the Nix flake; no manually installed Rust toolchain is required.
Environment
nix develop # rustc, cargo, rust-analyzer, cargo-nextest, clippy, mdbook
With direnv, direnv allow activates the same shell
automatically via the committed .envrc.
Development loop
cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo nextest run --locked
Formatting for the whole repository (Rust via rustfmt, Nix via nixfmt, and
Lisp sources via paredit edit format) is one command:
nix fmt
The verification gate
Pull requests run exactly one command, and the same command works locally:
nix flake check
It builds and runs every check the project defines:
| Check | What it verifies |
|---|---|
treefmt | Rust, Nix, and Lisp sources are canonically formatted |
actionlint | GitHub Actions workflows are well-formed |
clippy | No clippy warnings with -D warnings |
nextest | The full test suite under cargo-nextest |
package | The crate builds and its cargo test suite passes |
documentation | The mdBook site builds to a valid index.html |
lint-format-integration | The paredit-lint / paredit-format gates behave end to end |
Documentation is tested
The repository treats documentation as part of the public contract. Tests in
tests/cli/*_contract.rs read README.md, docs/src/*.md, action.yml, and
flake.nix and fail when documented commands, integration surfaces, or policy
statements drift from reality. When you change behaviour, update the
documentation in the same commit — CI enforces it.
To preview the book locally:
nix build .#docs # rendered site in ./result
mdbook serve docs # live-reloading preview from the dev shell
MSRV
The minimum supported Rust version is declared in Cargo.toml
(rust-version = "1.85"). Verify it before touching parser, refactor,
packaging, or public API surfaces:
cargo +1.85 test --locked
Releases
The release and compatibility guide defines the machine-output contract and upgrade expectations. Maintainers should use the root release checklist before publishing.
Releases and compatibility
The command reference in this documentation describes the current release.
Automation should pin the paredit-cli version it validates and run
paredit inspect capabilities --output json during upgrades.
Machine-readable output
Use --output json whenever a command offers it. JSON reports use a top-level
schema_version: fields may be added within a version, while field removals or
renames require a version bump. Consumers should reject unsupported schema
versions and tolerate unknown fields in supported versions.
Human-readable text output is intentionally not a machine contract and may
change between releases. inspect outline returns a bare JSON array and is the
documented exception to the top-level-object convention.
Command changes
Command paths, flags, defaults, and output schemas can change between releases. Release notes must identify changes that affect automation, and integrations must be validated against the command reference for the target version.
Maintainers follow RELEASING.md to verify the package, documentation, and Nix checks before publication.
Repository documentation
A typed Rust library API behind the CLI is available in the
paredit_cli documentation and its
source.
Project participation and operational policies live at the repository root: