paredit-cli is a Rust command line tool for safe S-expression refactoring.
It gives AI coding agents deterministic tree paths, byte spans, dialect hints,
and balanced structural edits so Lisp refactors do not devolve into manual
parenthesis surgery.
The core rule is: do not rewrite delimiters by hand. Validate the file, locate the exact form or symbol, apply a structural edit, then validate again.
- Extension-based Lisp dialect detection for Common Lisp, Emacs Lisp, Scheme, Clojure, Janet, and Fennel.
- Directory-root workspace discovery that turns
.lisp,.asd,.el,.scm,.clj,.janet, and.fnlfiles into parse/refactor inventories while skipping generated trees and symlinks by default. - Stable zero-based expression paths such as
0.2.1from the virtual document root. - Byte spans for every top-level form and atom occurrence.
- Exact atom search and rename that ignore comments and string contents.
- Common Lisp package declaration reports for
defpackageandin-packageplanning. - Common Lisp package rename plans that update package designators and qualified prefixes without touching comments, strings, or ordinary same-named atoms.
- Multi-file definition inventories with names, categories, spans, package context, and arity hints for decomposition and consolidation planning.
- Multi-file call-site inventories with list-head names, argument counts, spans, dialects, and enclosing definition context for arity refactors.
- Multi-file signature compatibility reports that compare callable definition arity with call-site argument counts before required-parameter changes.
- Agent-oriented refactor plans that combine impact gates, dependency checks, safe-to-automate decisions, and ordered command recommendations.
- Pre/post refactor verification gates that emit fixed JSON checks for AI coding agents and CI pipelines.
- Saved-manifest validation that checks refactor policy, content hashes, rewritten parse status, manifest consistency, and workspace root containment without writing files or rendering diffs.
- Top-level definition movement between files with plan-first JSON, dialect detection, missing-destination support, and reparse-before-write safety.
- Duplicate-shape replacement plans that turn repeated forms into per-file
replace-formsbatches for agent review. - Multi-file exact atom rename plans with explicit
--writeapplication. - Scoped exact atom rename inside one selected form for function-local or
let-local refactors. - Dialect-aware function extraction for turning a selected expression into a top-level helper definition.
- Dialect-aware local binding introduction for naming subexpressions without manual parenthesis surgery.
- Dialect-aware
letreports with binding paths, value spans, reference counts, and inline risk flags. - Plan-first unused local binding removal with reference checks, bulk zero-reference cleanup, and explicit value-drop approval for writes.
- Round-trip thread pipeline transforms that convert reviewed nested calls
into
->/->>forms and back without manual delimiter edits. - JSON reports designed for coding-agent planning and verification loops.
- Balanced edits: replace, kill, wrap, splice, raise, slurp, and barf.
- A typed Rust library API behind the CLI for downstream automation.
- DDD-oriented crate layout that separates typed Lisp-domain rules from CLI delivery concerns.
paredit check --file file.lisp
paredit dialect --file init.el
paredit stats --file system.asd --output json
paredit agent-report --file source.lisp --output json
paredit workspace-report --output json .
paredit workspace-refactor-plan --symbol render-pane --operation rename --fail-on-blocking-gate --require-definitions 1 --require-references 1 --output json .
paredit outline --file source.lisp --output json
paredit form-report --file source.lisp --path 0 --include-source --output json
paredit find-symbol --file source.lisp --symbol old-name --output json
paredit symbol-report --symbol old-name --output json src/*.lisp lisp/*.el
paredit call-report --symbol render-pane --output json src/*.lisp lisp/*.el
paredit signature-report --symbol render-pane --fail-on-mismatch --require-definitions 1 --require-calls 1 --output json src/*.lisp lisp/*.el
paredit call-graph --symbol render-pane --fail-on-inbound-callers --require-edges 1 --require-internal-edges 1 --output json src/*.lisp lisp/*.el
paredit impact-report --symbol render-pane --fail-on-risk-level warning --require-definitions 1 --require-references 1 --require-calls 1 --output json src/*.lisp lisp/*.el
paredit refactor-plan --symbol render-pane --operation rename --fail-on-blocking-gate --require-definitions 1 --require-references 1 --output json src/*.lisp lisp/*.el
paredit workspace-refactor-preview --from render-pane --to paint-pane --mode function --fail-on-no-change --fail-on-parse-error --fail-on-target-conflict --require-changed-files 1 --require-definitions 1 --require-edits 1 --output json .
paredit refactor-preview --from render-pane --to paint-pane --mode function --fail-on-no-change --fail-on-parse-error --fail-on-target-conflict --require-definitions 1 --require-edits 1 --output json src/*.lisp lisp/*.el
paredit refactor-check --manifest rename.preview.json --root . --output json
paredit refactor-status --manifest rename.preview.json --root . --output json
HASH=<manifest.hash from refactor-status JSON>
paredit refactor-diff --manifest rename.preview.json --expect-manifest-hash "$HASH" --root . --output json
paredit refactor-apply --manifest rename.preview.json --expect-manifest-hash "$HASH" --root . --output json
paredit refactor-apply --manifest rename.preview.json --expect-manifest-hash "$HASH" --root . --write --output json
paredit refactor-preview --from render-pane --to paint-pane --mode function --fail-on-no-change --fail-on-parse-error --fail-on-target-conflict --require-definitions 1 --require-edits 1 --write --output json src/*.lisp lisp/*.el
paredit verify-refactor --symbol render-pane --new-symbol paint-pane --operation rename --phase post --output json src/*.lisp lisp/*.el
paredit dependency-report --output json system.asd src/*.lisp lisp/*.el
paredit package-report --output json system.asd src/*.lisp
paredit definition-report --output json system.asd src/*.lisp lisp/*.el
paredit unused-definition-report --output json system.asd src/*.lisp lisp/*.el
paredit unused-definition-report --fail-on-unused --output json system.asd src/*.lisp lisp/*.el
paredit remove-unused-definitions --output json system.asd src/*.lisp lisp/*.el
paredit remove-unused-definitions --write system.asd src/*.lisp lisp/*.el
paredit remove-unused-definitions --include-exported --write system.asd src/*.lisp
paredit remove-definition --file src/core.lisp --path 2 --output json
paredit remove-definition --file src/core.lisp --path 2 --write
paredit move-definition --from-file src/core.lisp --to-file src/render.lisp --path 2 --output json
paredit move-definition --from-file src/core.lisp --to-file src/render.lisp --path 2 --write
paredit split-file --from-file src/core.lisp --to-file src/ui/render.lisp --path 2 --path 3 --output json
paredit split-file --from-file src/core.lisp --to-file src/ui/render.lisp --path 2 --path 3 --write
paredit split-file --from-file src/core.lisp --to-file src/ui/render.lisp --name render-pane --kind macro --output json
paredit move-form --from-file src/core.lisp --to-file src/system.lisp --path 2 --output json
paredit move-form --from-file src/core.lisp --to-file src/system.lisp --path 2 --insert before --anchor-path 1 --write
paredit duplicate-report --output json src/*.lisp test/*.lisp lisp/*.el
paredit replacement-plan --replacement "(run-case)" --output json src/*.lisp test/*.lisp lisp/*.el
paredit replace-forms --file test/suite.lisp --path 0 --path 1 --with "(run-case)" --require-same-shape --output json
paredit replace-forms --file test/suite.lisp --path 0 --path 1 --with "(run-case)" --require-same-shape --write
paredit add-export --file src/package.lisp --package demo --symbol #:new-api --output json
paredit add-export --file src/package.lisp --package demo --symbol #:new-api --write
paredit rename-package --from old.pkg --to new.pkg --output json system.asd src/*.lisp
paredit rename-package --from old.pkg --to new.pkg --write system.asd src/*.lisp
paredit rename-symbol --file source.lisp --from old-name --to new-name --plan --output json
paredit rename-symbol --file source.lisp --from old-name --to new-name
paredit rename-in-form --file source.lisp --path 0.3 --from old-name --to new-name --output json
paredit rename-in-form --file source.lisp --path 0.3 --from old-name --to new-name --write
paredit rename-binding --file source.lisp --path 0.3 --from old-name --to new-name --output json
paredit rename-binding --file source.lisp --path 0.3 --from old-name --to new-name --write
paredit rename-function --from old-name --to new-name --output json src/*.lisp lisp/*.el
paredit rename-function --from old-name --to new-name --write src/*.lisp lisp/*.el
paredit wrap-function-calls --function fetch-user --wrapper with-cache --all-calls --output json src/*.lisp lisp/*.el
paredit wrap-function-calls --function fetch-user --wrapper with-cache --call-path 0.4 --write src/service.lisp
paredit unwrap-call --file source.lisp --path 0.3 --function with-cache --output json
paredit unwrap-call --file source.lisp --path 0.3 --function with-cache --write
paredit thread-expression --file source.clj --path 0 --style last --output json
paredit thread-expression --file source.clj --path 0 --style last --write
paredit unthread-expression --file source.clj --path 0 --output json
paredit unthread-expression --file source.clj --path 0 --write
paredit rename-symbols --from old-name --to new-name src/*.lisp lisp/*.el
paredit rename-symbols --from old-name --to new-name --write src/*.lisp lisp/*.el
paredit extract-function --file source.lisp --path 0.3 --name helper --output json
paredit extract-function --file source.lisp --path 0.3 --name helper --write
paredit inline-function --file source.lisp --definition-path 0 --call-path 1.3 --output json
paredit inline-function --file source.lisp --definition-path 0 --all-calls --output json
paredit inline-function --file source.lisp --definition-path 0 --call-path 1.3 --remove-definition --write
paredit add-function-parameter --file source.lisp --definition-path 0 --name context --argument '*context*' --call-path 1.3 --output json
paredit add-function-parameter --file source.lisp --definition-path 0 --name context --argument '*context*' --all-calls --output json
paredit move-function-parameter --file source.lisp --definition-path 0 --name context --to-index 0 --call-path 1.3 --output json
paredit move-function-parameter --file source.lisp --definition-path 0 --name context --to-index 0 --all-calls --write
paredit swap-function-parameters --file source.lisp --definition-path 0 --left-name width --right-name height --call-path 1.3 --output json
paredit swap-function-parameters --file source.lisp --definition-path 0 --left-name width --right-name height --all-calls --write
paredit remove-function-parameter --file source.lisp --definition-path 0 --name context --call-path 1.3 --output json
paredit remove-function-parameter --file source.lisp --definition-path 0 --name context --all-calls --write
paredit introduce-let --file source.lisp --path 0.3.1 --name value --output json
paredit introduce-let --file source.lisp --path 0.3.1 --name value --all-occurrences --output json
paredit introduce-let --file source.lisp --path 0.3.1 --name value --write
paredit let-report --file source.lisp --fail-on-duplicate-evaluation --fail-on-unused-binding --require-inlineable-bindings 1 --output json
paredit inline-let --file source.lisp --path 0.3 --output json
paredit inline-let --file source.lisp --path 0.3 --write
paredit remove-unused-binding --file source.lisp --path 0.3 --name unused --output json
paredit remove-unused-binding --file source.lisp --path 0.3 --name unused --allow-drop-value --write
paredit remove-unused-binding --file source.lisp --path 0.3 --all-bindings --output json
paredit remove-unused-binding --file source.lisp --path 0.3 --all-bindings --allow-drop-value --write
paredit format --file source.lisp --indent 2
paredit select --file source.lisp --path 0.2
paredit select --file source.lisp --at 42
paredit replace --file source.lisp --path 0.1 --with new-name
paredit wrap --file source.lisp --path 0.2
paredit splice --file source.lisp --path 0.2
paredit raise --file source.lisp --path 0.2.1
paredit slurp-forward --file source.lisp --path 0
paredit slurp-backward --file source.lisp --path 1
paredit barf-forward --file source.lisp --path 0
paredit barf-backward --file source.lisp --path 0
paredit kill --file source.lisp --path 0.3All commands that accept --file read stdin when it is omitted.
| Dialect | Extensions |
|---|---|
| Common Lisp | lisp, lsp, cl, asd |
| Emacs Lisp | el |
| Scheme | scm, ss, sld |
| Clojure | clj, cljs, cljc, edn |
| Janet | janet |
| Fennel | fnl |
Use --dialect to override extension detection when stdin or generated files
do not carry a useful filename.
- Run
paredit workspace-report --output json .from the repository root to discover Lisp files, dialects, parse errors, definition counts, and call counts before choosing a refactor boundary. Reviewskippedcounts when generated, hidden, or unknown-extension files may be relevant. - Run
paredit workspace-refactor-plan --symbol old --operation rename --fail-on-blocking-gate --require-definitions 1 --require-references 1 --output json .when an agent should discover Lisp files from repository roots before producing the gated refactor plan. - Run
paredit workspace-refactor-preview --from old --to new --mode function --fail-on-no-change --fail-on-parse-error --fail-on-target-conflict --require-changed-files 1 --require-definitions 1 --require-edits 1 --output json .when an agent should discover Common Lisp, Emacs Lisp, Scheme, Clojure, Janet, or Fennel files by extension and produce exact byte-span edit scripts, content hashes, replacement-symbol conflict checks, and rewritten-output parse gates without hand-maintaining file globs. - Run
paredit check --file target.lisp. - Run
paredit agent-report --file target.lisp --output jsonand cache the top-level form paths and spans. - Use
paredit outline --output jsonto identify definition-like forms such asdefun,defmacro,defclass,defpackage,asdf:defsystem, and Emacs Lispdefcustomordefine-minor-mode. - Use
paredit form-report --path 0 --include-source --output jsonon the selected form before local rewrites. Reviewspan,head,definitionLike, child counts, depth, andsymbolsso an agent can decide whether a rename, extract, inline, or threading rewrite is scoped correctly. - Use
paredit package-report --output jsonon Common Lisp.asd,.lisp,.lsp, and.clfiles before package, nickname, export, or import refactors. Reviewdefpackage,in_packages,uses,imports, andexportsbefore changing package-qualified symbols. - Use
paredit dependency-report --output jsonacross explicit.asd, Common Lisp, and Emacs Lisp files before file moves, system splits, package cleanup, or dependency inversion. Reviewasdf-depends-on,asdf-component,require,provide,load,defpackage-*, andqualified-symbolentries to decide the safe edit order. - Use
paredit refactor-plan --symbol old --operation rename --fail-on-blocking-gate --require-definitions 1 --require-references 1 --output jsonfor an agent-ready preflight that combines impact gates, dependency-report reminders, safe-to-automate status, ordered commands, and CI-friendly policy failures for rename, remove, move, or signature refactors. - Use
paredit refactor-preview --from old --to new --mode function --fail-on-no-change --fail-on-parse-error --fail-on-target-conflict --require-definitions 1 --require-edits 1 --output jsonbefore write-mode refactors to inspect exact per-file rewrites, byte-span edit scripts, stable content hashes, output parse status, replacement-symbol conflict counts, and CI-friendly policy failures without modifying files. - Save preview JSON and run
paredit refactor-check --manifest rename.preview.json --root . --output jsonwhen CI or an AI agent needs a cheap manifest health gate without rendering a diff. JSON output includesmanifest.path,manifest.hash,manifest_policy_passed,manifest_outputs_parse,summary.can_apply, per-file hash/parse checks, and therootaudit object. - Save preview JSON and run
paredit refactor-status --manifest rename.preview.json --root . --output jsonwhen an AI coding agent needs a non-failing decision response before choosing the next tool call. JSON output includesstatus,next_action,blocked_reasons,write_plan,manifest.hash,summary.can_apply, per-file hash/parse checks, and therootaudit object. Userefactor-checkfor CI gating andrefactor-statusfor agent branching. - Save preview JSON and run
paredit refactor-diff --manifest rename.preview.json --expect-manifest-hash "$HASH" --root . --output jsonto render a machine-readable unified diff from the same byte-span edits while rechecking the pinned manifest hash, input hashes, output hashes, parse status, manifest consistency, and workspace-root containment without writing files. JSON output includes arootaudit object showing whether containment was enforced and which canonical root was used. - Save preview JSON and run
paredit refactor-apply --manifest rename.preview.json --expect-manifest-hash "$HASH" --root . --output jsonfor a second dry-run validation pass. Add--writeonly after the manifest hash pin, manifest policy, input hashes, output hashes, rewritten parse status, manifest consistency, and root containment all pass. JSON output includes arootaudit object for CI and agent logs. This is the safer AI-agent path because stale source files, modified manifests, or out-of-root manifest paths cannot be rewritten from an old manifest. - Use
paredit verify-refactor --symbol old --operation rename --phase pre --output jsonbefore edits andparedit verify-refactor --symbol old --new-symbol new --operation rename --phase post --output jsonafter edits to produce fixed pass/fail checks for AI coding agents and CI gates. - Use
paredit add-export --output jsonto plan a public API export after package review. The command updates an existing:export, creates one when missing, no-ops when the symbol is already exported, and reparses before--write. - Use
paredit rename-package --output jsonafter package review when renaming a Common Lisp package. Reviewdefpackage-name,in-package-name,package-option, andqualified-prefixoccurrences; the command preserves package designator prefixes, skips comments and strings, and reparses before--write. - Use
paredit definition-report --output jsonacross explicit.asd,.lisp,.cl, and.elfile sets before file decomposition, API surface cleanup, macro consolidation, or test-suite restructuring. Review each definition'scategory,name,path, span, package context,parameter_count, andbody_form_count. - Use
paredit call-report --output jsonacross the same explicit file set before callable rename, inline/extract, or function arity changes. Filter with--symbol namewhen planning one API, and review each call'spath, span,head,argumentCount, dialect, andenclosingDefinitionbefore selecting--call-pathvalues or applying a multi-file plan. Useparedit signature-report --symbol name --fail-on-mismatch --require-definitions 1 --require-calls 1 --output jsonbefore changing required parameters. It joins callable definitions with call sites across explicit files and reports each call asexact,missing-arguments,extra-arguments,unknown-definition, orambiguous-definition; the policy flags turn missing/extra argument discoveries and unexpectedly empty scans into CI failures. - Use
paredit call-graph --symbol name --fail-on-inbound-callers --require-edges 1 --require-internal-edges 1 --output jsonbefore file decomposition, definition moves, public API cleanup, or dead-code removal. Reviewinbound_edge_count, internal/external edge totals, andpolicy.violations; add--include-externalwhen external package, macro, or runtime dependencies affect the refactoring boundary. - Use
paredit unused-definition-report --output jsonbefore dead-code removal or public API shrinking. The report scans exact atom references across the explicit file set, excludes references inside the defining top-level form, and emitscandidatesplus per-definitionreference_countfor review. Add--fail-on-unusedto make CI fail when any externally unreferenced definition remains, or--require-unused-definitions Nwhen an agent expects a dead-code cleanup opportunity before planning removals. - Plan bulk dead-code cleanup with
paredit remove-unused-definitions --output jsonafter reviewingunused-definition-report. By default it removes only unreferenced non-protected definition categories, preserves definitions exported from Common Lispdefpackageforms, reports skipped package, system, test, customization, and mode definitions, deletes from the end of each file to avoid offset drift, and reparses before writing. Use--include-exportedonly after explicitly shrinking the public API, and--include-protectedonly after reviewing those protected categories. - Remove a reviewed dead top-level definition with
paredit remove-definition --output jsonfirst, then apply with--write. The command accepts the same top-levelpathreported bydefinition-reportandunused-definition-report, rejects non-definition forms, removes structurally, and reparses the file before writing. - Use
paredit duplicate-report --output jsonbefore table-driven test, helper extraction, macro consolidation, or repeated branch cleanup work. Review each shape'shead,form_path, span, node count, and original text before deciding whether the repetition is accidental duplication or a meaningful idiom. - Use
paredit replacement-plan --output jsonto convert reviewed duplicate shapes into per-filereplace-formscommand batches. Inspect each batch'spaths,replace_forms_args, replacement placeholder, and original form text before deciding the real helper, macro, or table-driven call. - Replace a reviewed batch of duplicate or table-driven candidate forms with
paredit replace-forms --output jsonbefore applying--write. Pass every reviewed--path, use--require-same-shapeforduplicate-report-derived batches, and inspecttargets,replacement_shape, andrewritten. - Move coherent top-level definitions between files with
paredit move-definition --output jsonafter reviewingdefinition-report. The command accepts a top-levelpath, supports a missing destination file as empty, removes the source form structurally, appends the balanced definition to the destination, and reparses both files before--write. - Split multiple reviewed top-level definitions into a new file or nested
directory with
paredit split-file --output jsonafter reviewingdefinition-report. Pass repeated--pathvalues for exact moves, or use--nameand--kindselectors to split a large file without manually collecting every path. Selector matches are de-duplicated, but a requested name or kind that matches nothing fails the plan. Plan mode reportsdefinition_count,from_rewritten,to_rewritten,to_file_existed, andto_parent_existedwithout creating files. With--write, the command creates the destination parent directory when needed, removes selected definitions from the source in reverse span order, appends them to the destination in source order, and reparses both rewritten files. - Move non-definition top-level forms with
paredit move-form --output jsonafter reviewingoutlineoragent-report. Use it fordefpackage,in-package,eval-when, ASDF fragments, feature conditionals, or migration scaffolding that is not a recognized definition. Inspecthead,text,from_rewritten, andto_rewritten; use--insert before/after --anchor-path PATHwhen the destination order is semantically important. - Use
paredit find-symbol --symbol name --output jsonfor a focused single-file scan, orparedit symbol-report --symbol name --output jsonfor an explicit file set. Review per-file counts and the outline context for each occurrence before any rename. Useparedit call-report --symbol name --output jsonwhen the rename, inline, extraction, or arity change depends on callable list-head sites rather than arbitrary atom references. Useparedit signature-report --symbol name --fail-on-mismatch --require-definitions 1 --require-calls 1 --output jsonwhen a required parameter change must be checked across Common Lisp and Emacs Lisp files; the policy flags fail empty scans and incompatible arity before writes. Useparedit call-graph --symbol name --fail-on-inbound-callers --require-edges 1 --require-internal-edges 1 --output jsonbefore moving, inlining, deleting, or splitting definitions; add--include-externalwhen external API and macro dependencies are part of the blast-radius review. Useparedit impact-report --symbol name --fail-on-risk-level warning --require-definitions 1 --require-references 1 --require-calls 1 --output jsonas the preflight gate before rename, move, remove, inline, extraction, or required-parameter edits. Reviewpolicy,riskLevel,risks,inbound_edge_count,non_call_reference_count, and signatureby_statusbefore applying write-mode commands. - Use
paredit rename-binding --output jsonwhen the target is a localletorlet*binding. Reviewbinding_span,reference_count, andshadowed_scope_count; the command skips nested scopes that rebind the same name. - Use
paredit rename-in-form --output jsonwhen the rename must stay inside a selected function, macro,let, or other local form. Review the scope span and occurrence count before applying--write. - Use
paredit rename-function --output jsonfor callable definitions (defun,defmacro,defgeneric,defmethod, and dialect equivalents). It rewrites definition names and list-head call sites, but does not rewrite arbitrary value references. - Use
paredit wrap-function-calls --output jsonwhen a refactor needs to introduce a wrapper macro or helper around reviewed call sites. Pass either--all-callsor repeated--call-path; reviewcalls,skippedAlreadyWrapped,skippedNested, and policy fields before applying--write. - Use
paredit unwrap-call --output jsonwhen a selected wrapper call should be replaced by one of its arguments. Pass--functionas a guard whenever possible, and reviewargumentIndex,argumentSpan,replacement, andrewrittenbefore applying--write. - Use
paredit thread-expression --output jsonwhen nested calls should be converted into a->or->>pipeline. Reviewbase,steps,replacement,span, and dialect before applying--write. - Use
paredit unthread-expression --output jsonwhen a reviewed thread pipeline should be converted back into nested calls. Standard->and->>operators infer the style; custom operators require explicit--style. - Use
paredit rename-symbol --plan --output jsonfor one file orparedit rename-symbols --output jsonfor an explicit file set after reviewingsymbol-report. - Apply a project-wide exact atom rename only with
paredit rename-symbols --write; the command re-parses every rewritten file before saving. - Extract duplicated or complex subexpressions with
paredit extract-function --output jsonfirst, then re-run with--writeafter reviewing the generated call and top-level definition. - Inline trivial or over-abstracted helpers with
paredit inline-function --output jsonfirst. Reviewdefinition_path,call_path, parameter reference counts, and the replacement before applying--write; pass--remove-definitiononly after confirming no remaining callers. - Add required function parameters with
paredit add-function-parameter --output jsonfirst. Review the selected definition, every explicit or discoveredcall_pathsentry, and the inserted argument before applying--write. Runsignature-reportacross the broader explicit file set first when callers can exist outside the single file being rewritten. - Reorder required function parameters with
paredit move-function-parameter --output jsonfirst. Reviewfrom_index,to_index, every explicit or discoveredcall_pathsentry, andmoved_argumentsbefore applying--write. - Swap two required function parameters with
paredit swap-function-parameters --output jsonfirst. Reviewleft_index,right_index, every explicit or discoveredcall_pathsentry, andswapped_argumentsbefore applying--write. - Remove obsolete required function parameters with
paredit remove-function-parameter --output jsonfirst. Reviewparameter_index, each selected or discovered call, andremoved_argumentsbefore applying--write. - Introduce names for complex intermediate expressions with
paredit introduce-let --output jsonfirst, then re-run with--writeafter reviewing the binding value and enclosing replacement. - Audit local bindings with
paredit let-report --output jsonbefore inlining. Review each form path, binding value span, reference count, and risk list. For agent or CI workflows, add--fail-on-duplicate-evaluation,--fail-on-unused-binding, and--require-inlineable-bindings Nto turn the report into a fixed pass/fail gate while still printing JSON. - Remove unnecessary single-binding
letforms withparedit inline-let --output jsonfirst. The command refuses unused bindings and duplicate evaluation by default; pass--allow-duplicate-evaluationonly after semantic review. - Remove unused local bindings with
paredit remove-unused-binding --output jsonafterlet-reportshowsreference_countis zero. Use--namefor a reviewed binding or--all-bindingsto remove every zero-reference binding in the selectedletorlet*. The command plans deletion without writing by default; pass--allow-drop-value --writeonly after reviewing that dropping each binding value expression does not remove a required side effect. - Use structural edits for form movement:
wrap,splice,raise,slurp-*, andbarf-*. - Run
paredit checkagain, then run the project test suite.
This workflow is intended for large Common Lisp and Emacs Lisp refactors where the safe primitive operations are: discover definitions, isolate forms, rename symbols exactly, move balanced forms, and verify after every generated change.
Detect an Emacs Lisp file:
paredit dialect --file init.el --output jsonFind all exact uses of a Common Lisp symbol without matching strings or comments:
paredit find-symbol --file src/core.lisp --symbol make-session --output jsonPlan a rename before applying it:
paredit rename-symbol \
--file src/core.lisp \
--from old-session-name \
--to session-name \
--plan \
--output jsonApply the rename into a temporary file and re-check it:
paredit rename-symbol \
--file src/core.lisp \
--from old-session-name \
--to session-name > /tmp/core.lisp
paredit check --file /tmp/core.lispPlan and apply a rename only inside one selected form:
paredit rename-in-form \
--file src/core.lisp \
--path 0.3 \
--from session \
--to tmux-session \
--output json
paredit rename-in-form \
--file src/core.lisp \
--path 0.3 \
--from session \
--to tmux-session \
--writePlan and apply a lexical local binding rename:
paredit rename-binding \
--file src/core.lisp \
--path 0.3 \
--from session \
--to tmux-session \
--output json
paredit rename-binding \
--file src/core.lisp \
--path 0.3 \
--from session \
--to tmux-session \
--writePlan and then apply an exact atom rename across a Common Lisp or Emacs Lisp file set:
paredit rename-symbols \
--from old-session-name \
--to session-name \
--output json \
src/*.lisp elisp/*.el
paredit rename-symbols \
--from old-session-name \
--to session-name \
--write \
src/*.lisp elisp/*.elExtract a complex expression into a top-level helper:
paredit extract-function \
--file src/renderer.lisp \
--path 0.3 \
--name render-fragment \
--param width \
--param height \
--output json
paredit extract-function \
--file src/renderer.lisp \
--path 0.3 \
--name render-fragment \
--param width \
--param height \
--insert before \
--anchor-path 2 \
--writeextract-function keeps parameter discovery explicit: pass --param in
call order when the extracted expression depends on local names. This keeps
agent-generated refactors deterministic and reviewable instead of relying on
implicit lexical inference.
Convert nested calls into a reviewed thread-last pipeline:
paredit thread-expression \
--file src/pipeline.clj \
--path 0 \
--style last \
--output json
paredit thread-expression \
--file src/pipeline.clj \
--path 0 \
--style last \
--writethread-expression defaults to -> for --style first and ->> for
--style last; pass --operator for dialect-specific threading macros after
reviewing the JSON plan.
Remove a reviewed wrapper call while keeping one argument:
paredit unwrap-call \
--file src/service.lisp \
--path 0.3 \
--function with-cache \
--argument-index 0 \
--output json
paredit unwrap-call \
--file src/service.lisp \
--path 0.3 \
--function with-cache \
--argument-index 0 \
--writeunwrap-call is intentionally local: select one parenthesized call with
--path or --at, optionally guard the head with --function, then replace
the whole call with the selected zero-based argument.
Convert a reviewed thread pipeline back into nested calls:
paredit unthread-expression \
--file src/pipeline.clj \
--path 0 \
--output json
paredit unthread-expression \
--file src/pipeline.clj \
--path 0 \
--writeunthread-expression infers --style first from -> and --style last from
->>. Pass both --operator and --style when a project-specific threading
macro uses a different name.
Inline a reviewed helper call back into its caller:
paredit inline-function \
--file src/renderer.lisp \
--definition-path 0 \
--call-path 3.2 \
--output json
paredit inline-function \
--file src/renderer.lisp \
--definition-path 0 \
--all-calls \
--remove-definition \
--writeinline-function is intentionally conservative. It requires a supported
single-expression function definition, exact arity, and simple positional
parameters. It refuses duplicate argument evaluation and unused arguments by
default; use --allow-duplicate-evaluation or --allow-drop-arguments only
after reviewing the JSON plan. Pass repeated --call-path values for reviewed
specific calls, or --all-calls to discover every same-file call whose list
head matches the selected definition. The JSON plan reports both the legacy
single-call fields and a calls array so agents can review each replacement.
Add a required parameter to a reviewed definition and selected call sites:
paredit add-function-parameter \
--file src/renderer.lisp \
--definition-path 0 \
--name context \
--argument '*context*' \
--call-path 3.2 \
--output json
paredit add-function-parameter \
--file src/renderer.lisp \
--definition-path 0 \
--name context \
--argument '*context*' \
--all-calls \
--writeadd-function-parameter updates only the selected function definition and the
reviewed --call-path entries, or every same-file call discovered by
--all-calls. It detects supported Lisp function forms from the file extension
or --dialect, verifies each call head against the selected definition,
reports the final call_paths, re-parses the rewritten file, and supports
--insert start for prefix arguments.
Move a required parameter within a reviewed definition and selected call sites:
paredit move-function-parameter \
--file src/renderer.lisp \
--definition-path 0 \
--name context \
--to-index 0 \
--call-path 3.2 \
--output json
paredit move-function-parameter \
--file src/renderer.lisp \
--definition-path 0 \
--name context \
--to-index 0 \
--all-calls \
--writemove-function-parameter reorders only a simple positional parameter in the
selected definition and moves the same positional argument in each reviewed
--call-path entry, or each same-file call discovered by --all-calls. It
reports from_index, to_index, call_paths, and moved_arguments, verifies
each call head against the selected definition, and re-parses the rewritten
file.
Swap two required parameters within a reviewed definition and selected call sites:
paredit swap-function-parameters \
--file src/renderer.lisp \
--definition-path 0 \
--left-name width \
--right-name height \
--call-path 3.2 \
--output json
paredit swap-function-parameters \
--file src/renderer.lisp \
--definition-path 0 \
--left-name width \
--right-name height \
--all-calls \
--writeswap-function-parameters swaps only simple positional parameters in the
selected definition and swaps the same positional arguments in each reviewed
--call-path entry, or each same-file call discovered by --all-calls. It
reports left_index, right_index, call_paths, and swapped_arguments,
verifies each call head against the selected definition, refuses calls missing
either argument, and re-parses the rewritten file.
Remove an obsolete required parameter from a reviewed definition and selected call sites:
paredit remove-function-parameter \
--file src/renderer.lisp \
--definition-path 0 \
--name context \
--call-path 3.2 \
--output json
paredit remove-function-parameter \
--file src/renderer.lisp \
--definition-path 0 \
--name context \
--all-calls \
--writeremove-function-parameter removes only a simple positional parameter from the
selected definition and the same positional argument from each reviewed
--call-path entry, or each same-file call discovered by --all-calls. It
verifies each call head against the selected definition, reports
parameter_index, call_paths, and removed_arguments, refuses missing call
arguments by default, and re-parses the rewritten file.
Introduce a local name for a complex subexpression:
paredit introduce-let \
--file src/renderer.lisp \
--path 0.3.1 \
--name fragment \
--output json
paredit introduce-let \
--file src/renderer.lisp \
--path 0.3.1 \
--name fragment \
--all-occurrences \
--output json
paredit introduce-let \
--file src/renderer.lisp \
--path 0.3.1 \
--name fragment \
--writeBy default, introduce-let replaces only the selected expression. Add
--all-occurrences to replace every structurally identical expression inside
the enclosing list; JSON output includes occurrence_count and
occurrence_spans for review before --write. Equivalent expressions under a
nested binding form that already binds the introduced name are skipped to avoid
accidental capture; review skipped_shadowed_occurrence_count and
skipped_shadowed_occurrence_spans when using --all-occurrences.
Audit local bindings before inlining them:
paredit let-report \
--file src/renderer.lisp \
--fail-on-duplicate-evaluation \
--fail-on-unused-binding \
--require-inlineable-bindings 1 \
--output jsonInline a reviewed single-binding local name:
paredit inline-let \
--file src/renderer.lisp \
--path 0.3 \
--output json
paredit inline-let \
--file src/renderer.lisp \
--path 0.3 \
--writeRemove a reviewed unused local binding:
paredit remove-unused-binding \
--file src/renderer.lisp \
--path 0.3 \
--name scratch \
--output json
paredit remove-unused-binding \
--file src/renderer.lisp \
--path 0.3 \
--name scratch \
--allow-drop-value \
--write
paredit remove-unused-binding \
--file src/renderer.lisp \
--path 0.3 \
--all-bindings \
--output json
paredit remove-unused-binding \
--file src/renderer.lisp \
--path 0.3 \
--all-bindings \
--allow-drop-value \
--write- Rust edition 2024 with a minimum supported Rust version in
Cargo.toml. unsafe_code = "forbid".- Newtypes for byte offsets, byte spans, expression paths, node ids, child indexes, and symbol names.
thiserrorfor parse errors andanyhowfor CLI boundary errors.- Warning-clean
cargo clippy --all-targets -- -D warnings. - Nix flake verification for reproducible development.
The crate follows a DDD-oriented module layout:
src/
domain/ Typed S-expression model, parser, Lisp dialect rules
application/ Use-case orchestration boundary
infrastructure/ Filesystem and process adapter boundary
presentation/ CLI parser, command dispatch, output formatting
Keep Lisp semantics, structural edit invariants, and dialect detection in
domain. Keep anyhow, clap, terminal output, and write/no-write command
behavior at the presentation boundary unless a use case is promoted into
application.
nix develop
cargo fmt --all
cargo clippy --all-targets -- -D warnings
cargo test
nix flake check
nix build .#paredit-cli is a structural S-expression tool, not a Lisp evaluator or full
reader implementation. It preserves balanced list, vector, and map delimiters;
tracks comments and strings safely for symbol operations; and provides
dialect-aware definition hints. It does not macroexpand code or update ASDF,
package, autoload, or module manifests automatically.