Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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, --diff for a unified diff, --write to 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 inspect reads and reports without writing.
  • paredit edit transforms one selected form; stdout by default, --diff for a unified diff, --write to update the file in place.
  • paredit refactor plans, 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.

CommandPurpose
checkValidate that input is a balanced S-expression document.
dialectDetect Lisp dialect from --file extension or explicit --dialect.
statsPrint parse, dialect, and structural metrics for agent planning.
agent-reportPrint a complete JSON report for AI coding agent refactor planning.
capabilitiesPrint a machine-readable catalog of every command, flag, default, and enum value.
outlinePrint top-level forms with paths, spans, and definition hints.
formReport one selected form with local structure for refactor planning.
find-symbolFind exact atom occurrences without touching strings or comments.
symbolsReport exact atom occurrences across explicit files for rename planning.
callsReport list-head call sites across explicit files for arity refactor planning.
signatureCompare callable definitions and call-site arity across explicit files.
call-graphReport internal and optional external call graph edges.
impactReport refactoring impact risks for one symbol across explicit files.
workspaceDiscover Lisp sources under roots and report parse/refactor inventory.
dependenciesReport package, system, load, and qualified-symbol dependencies.
packagesReport Common Lisp package declarations across explicit files.
definitionsReport definition-like top-level forms across explicit files.
unused-definitionsReport definitions with no external exact atom references.
duplicatesReport repeated structural S-expression shapes across explicit files.
similarityReport structurally similar S-expression forms across explicit files.
letsReport 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.
CommandPurpose
formatPrint a canonical, indentation-based rendering.
repair-unclosed-listsAppend matching delimiters for parser-detected unclosed lists; refuse all other parse errors.
selectPrint the S-expression selected by --path or --at.
replaceReplace the selected S-expression with replacement text.
killRemove the selected S-expression.
wrapWrap the selected S-expression in a new list.
spliceRemove one list pair while keeping its children.
raiseReplace the selected expression’s parent list with the selection.
transpose-forwardExchange the selected expression with its next sibling while keeping trivia in place.
transpose-backwardExchange the selected expression with its previous sibling while keeping trivia in place.
slurp-forwardPull the next sibling into the selected list.
slurp-backwardPull the previous sibling into the selected list.
barf-forwardPush the last child out of the selected list.
barf-backwardPush 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

CommandPurpose
planProduce an ordered, gated refactoring plan for AI coding agents.
verifyVerify pre/post refactoring invariants for agents and CI gates.
previewPreview exact refactoring rewrites without modifying files.
checkValidate a refactor preview manifest without writing files.
statusSummarize a preview manifest into agent-safe next actions.
applyApply a previously generated preview manifest with hash guards.
diffRender a verified diff from a preview manifest without writing files.
workspace-planDiscover Lisp sources under roots and build a gated refactor plan.
workspace-previewDiscover sources and preview exact refactoring rewrites.
workspace-executeExecute a workspace refactor with preview gates and post-write verification.

Definition and file layout

CommandPurpose
remove-definitionPlan or remove a top-level definition from one file.
remove-unused-definitionsPlan or remove unused top-level definitions across files.
move-definitionPlan or move a top-level definition between files.
split-filePlan or split multiple top-level definitions into another file.
sort-definitionsPlan or sort contiguous top-level definition blocks in one file.
move-formPlan or move any top-level form between files.
insert-top-levelInsert exactly one top-level S-expression before, after, or at the end of a file.
replacement-planConvert duplicate groups into reviewed replace-forms batches.
replace-formsPlan or replace multiple reviewed forms in one file.

Packages

CommandPurpose
add-exportPlan or add a symbol to a Common Lisp defpackage :export option.
sort-package-exportsPlan or sort defpackage :export symbol designators.
sort-package-optionsPlan or sort defpackage option forms.
merge-package-optionsPlan or merge duplicate defpackage option forms.
rename-packagePlan or rename package designators and qualified prefixes.

Renames

CommandPurpose
rename-atRename whatever symbol occupies a byte offset, dispatching to the owning namespace and scope.
rename-symbolRename exact atom occurrences without touching strings or comments.
rename-in-formRename exact atom occurrences inside one selected form.
rename-bindingRename one local binding and only the references in its lexical scope.
rename-symbolsPlan or apply an exact atom rename across explicit files.
rename-functionPlan or apply a Common Lisp callable definition and designator rename.
rename-macroletPlan or apply a macrolet/compiler-macrolet binding and call-site rename.
rename-symbol-macroPlan or apply a define-symbol-macro binding and value-reference rename.
rename-local-functionPlan or apply a flet/labels local function binding and call-site rename.

Calls and functions

CommandPurpose
replace-function-callsPlan or replace callable call-site heads across explicit files.
wrap-function-callsPlan or wrap callable call sites in another function or macro call.
unwrap-function-callsPlan or remove a unary wrapper around callable call sites.
unwrap-callReplace one selected wrapper call with one selected argument.
thread-expressionConvert a nested call chain into a thread-first or thread-last pipeline.
unthread-expressionConvert a threading pipeline back into nested calls.
extract-functionExtract the selected expression into a top-level function with inferred parameters.
extract-local-functionExtract the selected expression into a Common Lisp flet or labels binding.
extract-constantExtract the selected expression into a top-level constant.
inline-functionInline one selected function call using a selected function definition.
inline-lambdaReplace a safe, immediately invoked Common Lisp lambda with a parallel let.
inline-local-functionInline the sole direct call in a safe, single-binding Common Lisp flet form.
inline-symbol-macroExpand a conservative single-binding Common Lisp symbol-macrolet form.
inline-literal-constantInline an immutable self-evaluating Common Lisp defconstant value.
convert-labels-to-fletConvert a non-recursive Common Lisp labels form into flet.
convert-flet-to-labelsConvert a Common Lisp flet form into labels when definition references cannot be captured.
rename-blockRename a selected Common Lisp block and matching return-from references.
rename-tagRename one tag in a selected Common Lisp tagbody and matching go references.
remove-unused-blockRemove a selected Common Lisp block with no matching return-from.
remove-unused-tagRemove an unreferenced tag from a selected Common Lisp tagbody.

Parameters and bindings

CommandPurpose
add-function-parameterAdd a parameter to a selected function and explicit call sites.
move-function-parameterMove one positional parameter in a function and its call sites.
swap-function-parametersSwap two positional parameters in a function and its call sites.
reorder-function-parametersReorder all positional parameters in a function and its call sites.
remove-function-parameterRemove one positional parameter from a function and its call sites.
introduce-letReplace the selected expression with a local binding in the enclosing list.
inline-letInline a single local let binding into its body.
convert-let-to-let-starConvert a Common Lisp or Emacs Lisp let to let* when later initializers do not reference earlier bindings.
convert-let-star-to-letConvert a Common Lisp let* to let when later initializers do not reference earlier bindings.
convert-do-star-to-doConvert a Common Lisp do* to do when later initializers and step expressions do not reference earlier bindings.
convert-prog-star-to-progConvert a Common Lisp prog* to prog when later initializers do not reference earlier bindings.
merge-nested-let-starMerge a directly nested Common Lisp or Emacs Lisp let* into one sequential binding form.
split-let-starSplit a Common Lisp or Emacs Lisp let* into nested sequential binding forms at --binding-index.
merge-nested-letMerge directly nested Common Lisp or Emacs Lisp parallel let forms when inner initializers are independent.
merge-nested-fletMerge directly nested Common Lisp flet forms when inner definitions do not reference outer local functions.
split-letSplit a Common Lisp or Emacs Lisp parallel let at --binding-index without capturing initializer references.
eliminate-empty-binding-formRemove an empty Common Lisp or Emacs Lisp let or let* from a known expression position.
flatten-prognFlatten directly nested Common Lisp or Emacs Lisp progn forms in a safe expression context.
convert-if-to-condConvert a Common Lisp or Emacs Lisp (if test then [else]) form to cond.
convert-cond-to-ifConvert simple Common Lisp or Emacs Lisp cond clauses to nested if forms.
convert-when-to-ifConvert a Common Lisp or Emacs Lisp when form to if.
convert-unless-to-ifConvert a Common Lisp or Emacs Lisp unless form to if.
convert-if-to-whenConvert a Common Lisp or Emacs Lisp if without a meaningful else to when.
convert-if-to-unlessConvert a Common Lisp or Emacs Lisp if with a literal nil then branch to unless.
remove-unused-bindingPlan 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 0 selects the whole defun.
  • --path 0.0 selects the atom defun.
  • --path 0.2 selects the parameter list (x).
  • --path 0.3 selects the body form (+ x 1).
  • --path 1.2 selects 10.

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

  1. Discover with paredit inspect.
  2. Review an edit result (--diff or stdout) before passing --write.
  3. Plan, preview, and verify a refactor before applying it.
  4. 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

CodeMeaning
0Success. For plan/preview commands: the report was produced and no requested gate failed.
1Operational failure: parse errors, missing targets, refused writes.
2Usage error: unknown command, unknown flag, or invalid value (from argument parsing).
3Policy 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 json is 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 (currently 1). New fields may be added within a version; renames or removals bump it. (inspect outline emits a bare array and is the one exception.)
  • JSON reports go to stdout; diagnostics and errors go to stderr as text.
  • paredit edit commands print the whole rewritten document to stdout by default. --diff switches stdout to a unified diff; --write persists the result to --file instead and prints nothing (combine with --diff to 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: planpreviewverify --phase pre--write (or manifest apply with hash guards) → verify --phase post.

Rules of thumb for agents

  1. Never hand-edit balanced delimiters; every structural change goes through a paredit command.
  2. Run paredit inspect check before and after a batch of edits.
  3. Never pass --write until a no-write preview (--diff, plan JSON, or preview manifest) has been reviewed.
  4. Use the narrowest command that matches the binding kind: rename-function, rename-binding, rename-macrolet, … before falling back to the generic rename-symbol.
  5. Prefer --path from a report over --at guesses; reserve --at for 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
InputDefaultMeaning
modelintlint 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.
versionpinned refparedit-cli git ref to run; defaults to the ref the action is pinned to.
cachix-nametakeokunn-paredit-cliPublic 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 for checks.
  • 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 .fnl sources.
  • overlays.default — adds paredit-cli, paredit-lint, paredit-format, and paredit-format-files to 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:

CheckWhat it verifies
treefmtRust, Nix, and Lisp sources are canonically formatted
actionlintGitHub Actions workflows are well-formed
clippyNo clippy warnings with -D warnings
nextestThe full test suite under cargo-nextest
packageThe crate builds and its cargo test suite passes
documentationThe mdBook site builds to a valid index.html
lint-format-integrationThe 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: