Skip to content

techascent/cljdx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cljdx

A Clojure source structural index and form extractor, designed so an LLM can read a .clj/.cljs/.cljc file by structure and line range instead of by scrolling, and pull out byte-exact form text suitable as the old_string argument to an Edit tool call.

This README is written for LLM agents. If you are one, read it once and treat cljdx as your default lens onto Clojure code in this repo.

Why prefer cljdx over Read for Clojure

Read gives you raw lines. For non-trivial Clojure files this is wasteful:

  • You don't know where a function starts/ends without parsing parens yourself.
  • You can't tell which forms are top-level vs nested.
  • When you want to Edit a specific form, you have to copy it line-by-line and hope your indentation matches exactly. If it doesn't, Edit fails because old_string isn't found.

cljdx gives you:

  • A compact tree of forms with line ranges, depth, and a short anchor preview.
  • Stable form IDs (process-batch, process-batch.2.1, top-3.1, …) that you can reference in follow-up calls.
  • An extract mode that prints the exact bytes of any form — paste it straight into an Edit call.

Rule of thumb: for any non-trivial work on .clj/.cljs/.cljc, run cljdx FILE first instead of Read FILE.

Installation

cljdx is a babashka script. Requires bb on PATH.

Clone and symlink the launcher onto your PATH:

git clone https://github.com/techascent/cljdx ~/src/cljdx
ln -s ~/src/cljdx/bin/cljdx ~/.local/bin/cljdx

The launcher resolves its own canonical path, so the symlink works regardless of where you invoke cljdx from.

Verify:

cljdx --help

Basic use

Three things you'll do, in roughly this order.

1. Get an overview

cljdx src/myns/core.clj

Output shape (one form per line):

<id>                 <line-range>  d<depth>  <anchor> [×N if anchor not unique]

Example:

ns                   L1-L11     d0  (ns myns.core
profiler-controls    L18-L30    d0  (defn- profiler-controls []
profiler-controls.1  L18        d1  [] [×2]
profiler-controls.2  L19-L30    d1  (let [^String status-msg (core/status)]
  • id is symbol-rooted at the top level (profiler-controls), and dotted-positional below it (profiler-controls.2 is the 2nd child form). Anonymous top-level forms get top-N ids.
  • d0 is top-level; d1 is one level deep. The default index shows depths 0 and 1.
  • [×2] means the anchor text occurs more than once in the file. Relevant for extract — see below.

2. Drill into one top-level form

cljdx src/myns/core.clj profiler-controls

Shows the full subtree under profiler-controls to arbitrary depth. Use this when the overview shows the form you care about but you need to see its inner structure (let-bindings, nested branches, etc.).

3. Extract a form for editing

cljdx extract src/myns/core.clj profiler-controls
  • stdout: raw bytes of the form. No line numbers, no decoration. Byte-exact — copy this into the old_string of an Edit call and it will match.
  • stderr: one-line header:
    form: profiler-controls  L18-L30  312 bytes  occurrences-in-file: 1
    

If occurrences-in-file is > 1, the form's text appears more than once in the file and Edit will refuse to apply because old_string is ambiguous. Either widen the snippet (include surrounding context manually) or pick a unique parent form.

FILE may be - to read from stdin (handy with git show, etc.).

Recommended workflow

For "modify function foo in src/myns/core.clj":

  1. cljdx src/myns/core.clj — confirm foo exists and note its range.
  2. cljdx src/myns/core.clj foo — inspect inner structure if you need to pick a sub-form.
  3. cljdx extract src/myns/core.clj foo — capture exact bytes.
  4. Issue Edit with the extracted text as old_string and the rewritten form as new_string.

This avoids the most common Clojure edit failure mode: paren/indent mismatch between what the LLM thinks the code looks like and what's on disk.

Reader errors

If the file has unbalanced delimiters, cljdx emits a reader error with an indent-divergence hint — a line number where the indentation stops making sense given the paren stack. This is often far from where the reader finally notices the mismatch, and is usually the line you actually need to fix.

Discoverability: tell Claude about cljdx in your global CLAUDE.md

An LLM only uses cljdx if it knows the tool exists. The most reliable way to make that true across every project is a short blurb in your global ~/.claude/CLAUDE.md — that file is loaded into every Claude Code session regardless of working directory. Suggested wording:

## Clojure source analysis

For any non-trivial work on `.clj`/`.cljs`/`.cljc` files, use `cljdx`:
`cljdx FILE` for a line-addressed structural index, `cljdx FILE NAME` for
the full subtree of a top-level form, `cljdx extract FILE FORM-ID` for
byte-exact form bytes suitable as `old_string` in Edit. Reader-error
output includes an indent-divergence hint pointing at the likely culprit
line. Run `cljdx --help` for details.

For a single project, the same blurb in the project's CLAUDE.md works too, but global is better — most LLM sessions don't know what tools are on PATH unless something tells them.

Configuration: reducing permission prompts

Claude Code prompts on every shell invocation by default. Pre-allow cljdx in settings.json so it runs silently.

User-level (~/.claude/settings.json):

{
  "permissions": {
    "allow": [
      "Bash(cljdx)",
      "Bash(cljdx *)"
    ]
  }
}

Bash(cljdx) covers the bare cljdx --help invocation; Bash(cljdx *) covers all subcommands and arguments. Add both.

If you only want this for one project, put the same block in .claude/settings.json (committed) or .claude/settings.local.json (personal) at the project root.

Project layout

bin/cljdx          # launcher script (bb)
src/cljdx/main.clj # all logic
test/cljdx/        # tests
bb.edn             # bb task entry: `bb cljdx ...`

Running the test suite:

cd ~/src/cljdx && bb test

About

Structural index and byte-exact form extractor for Clojure source — built for LLMs editing .clj/.cljs/.cljc safely.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors