Bring your Spring Boot app. Add one dependency, copy one file, and ask Claude to dig in. No restarts. No boilerplate.
Run this against any Spring Boot app already running on your machine — no dependency, no code changes, no restart. Great for a quick look before committing to the full install.
jshell # then at the jshell prompt, paste: /open https://raw.githubusercontent.com/brdloush/livewire/refs/heads/main/attach.jsh
⚠️ Java 21+: start your app with
-XX:+EnableDynamicAgentLoading to allow dynamic agent injection.
Once attached you get info(), beans(), eval(),
sql(), and demo() at the jshell prompt.
For repeated use, full SQL tracing, and the query-watcher, follow the steps below —
the dependency install is the recommended path.
Three small changes: the Maven dependency, one property to enable it locally, and the agent skill file that tells your AI agent exactly what Livewire can do.
Add Livewire as a dependency. Scope it to your dev profile so it never ships to production.
<dependency> <groupId>net.brdloush</groupId> <artifactId>livewire</artifactId> <version>0.12.0</version> </dependency>
Enable Livewire in your local-only config. Don't commit this file —
it should already be in your .gitignore.
# application-local.yml livewire: enabled: true # nrepl.port: 7888 # optional
The Livewire skill uses
clojure-mcp-light's
clj-nrepl-eval CLI to talk to the nREPL server.
Install it once via bbin
(Babashka's binary installer):
# 1. Install bbin itself (once) brew install babashka/brew/bbin # 2. Install clj-nrepl-eval (once) bbin install https://github.com/bhauman/clojure-mcp-light.git \ --tag v0.2.1 \ --as clj-nrepl-eval \ --main-opts '["-m" "clojure-mcp-light.nrepl-eval"]'
The skill is the agent's cheat-sheet for Livewire's full API. It ships as
a directory with three parts: SKILL.md (the lean core reference),
bin/ (shell wrapper scripts like lw-start,
lw-call-endpoint, lw-jpa-query, and more), and
references/ (topic-specific deep-dives the agent loads on demand).
Copy or symlink the whole directory.
The skill is designed for agents that support slash-command skills
(such as Claude Code
or ECA).
Place the whole directory at
.claude/skills/livewire/ — both tools pick it up from there.
# first, clone the livewire repo (just for the skill files) git clone https://github.com/brdloush/livewire # copy the skill directory (SKILL.md + bin/ scripts) cp -r livewire/skills/livewire <your-project>/.claude/skills/livewire # or symlink it — git pull to stay up to date ln -s /path/to/livewire/skills/livewire \ <your-project>/.claude/skills/livewire
Start it with the local profile active so Livewire picks up the
property you set. The nREPL server starts automatically — look for this line in the logs:
The bloated-shelf sample app is a Spring Boot library API with 200 books, 30 authors, 50 members, and N+1 problems deliberately baked in. It's the canonical Livewire sandbox.
cd bloated-shelf mvn spring-boot:run \ -Dspring-boot.run.profiles=dev,seed,local
Open your agent in your project directory. Then load the Livewire skill — this primes it with the full API reference and the right workflow for exploring a live Spring app.
Works with any agent that supports slash-command skills — including
Claude Code,
ECA,
and others. Once the skill file is in .claude/skills/livewire/,
load it with:
Type this slash command in the agent chat. It will confirm the skill has loaded, then connect to the running nREPL on port 7888. You're live.
No Clojure knowledge required. Just describe what you want to know about your running app — Claude handles the REPL calls and presents the results. Here are some prompts to get you started.
What entities does this app have? Describe their relationships and fetch strategies.
Claude calls intro/list-entities and intro/inspect-entity
on each one, then summarises the full schema — table names, column mappings,
associations, and fetch types — without reading a single source file.
List all HTTP endpoints. For each one, show the required roles and what parameters it expects.
intro/list-endpoints returns every route with its @PreAuthorize
expression, plus resolved :required-roles / :required-authorities
vectors so you can construct a lw/run-as call mechanically.
Each parameter carries :source (:path, :query, :body, :header),
:required, and :default-value when applicable.
Which beans have the most dependencies? Are there any that look like they could be split up?
Claude calls lw/all-bean-deps to get the full wiring graph,
pre-filtered to your own application beans (auto-detected via
@SpringBootApplication — no configuration needed). It sorts by
dependency count to surface high-fan-out candidates. In bloated-shelf this
immediately surfaces gottaInjectThemAllService — a service that
injects every single repository in the app. Its name is the punchline; the
wiring graph is the proof. Claude also spots architectureBreakingController
on the high-dependents side of bookRepository — a controller
wiring a repository directly, bypassing the service layer entirely.
Give me the full @Transactional map of the app. Which methods are read-only and which ones write? Are there any surprising configurations?
Claude calls lw/all-bean-tx to map the effective
@Transactional configuration of every app bean at runtime —
propagation, isolation, read-only flag, rollback rules. No source reading
needed. In a well-configured app like bloated-shelf, every get*
and find* method comes back read-only: true, and
archiveBook stands out as the only write path —
exactly one method with read-only: false. The configuration
validates cleanly. In a less careful app, this call surfaces the surprises.
How many SQL queries does GET /api/books actually execute? Show me the breakdown.
Claude wraps the controller call in trace/trace-sql and reports
the exact count with caller attribution. In bloated-shelf the answer is
1,201 queries for 200 books — a number
that makes the problem undeniable.
Find the N+1 query problem on the books endpoint. Which queries are repeating?
trace/detect-n+1 groups repeated query templates and flags the
worst offenders with counts. Claude names both the query and the likely
association that's causing it.
How many queries does GET /api/admin/most-loaned fire compared to GET /api/books?
The contrast is stark: the admin aggregate fires 1 query via a single GROUP BY JOIN, while the books list fires over a thousand. Side-by-side, it makes a compelling case for the fix.
Which genres have the most books in this library? And which ones are borrowed the most?
Claude uses jpa/jpa-query to answer both questions with JPQL
aggregations — no raw SQL, no schema guessing. It first runs
intro/inspect-entity to discover that the many-to-many lives
on Book.genres (not on Genre), then traverses
from the Book side correctly. Results come back as named maps:
Historical Fiction leads in book count (35), Poetry leads in loans (74).
Are there any overdue loans right now? Show me the member name, book title, and how many days overdue.
Claude writes and runs a JPQL query against the live EntityManager
— traversing LoanRecord → member.fullName and
LoanRecord → book.title by property path, no column name guessing
required. In bloated-shelf the answer is
104 overdue loans, ranked oldest
first. The kind of operational insight that would normally mean a new
repository method, a recompile, and a restart.
Which authors have the highest average review rating? Show top 10.
Claude writes a JPQL aggregation —
AVG(r.rating) grouped by author, joined through
Author → books → reviews — and executes it via
jpa/jpa-query. Results come back as named Clojure maps with
review counts alongside the average, so you can spot authors with suspiciously
high ratings from only a handful of reviews.
Pure ad-hoc analysis — no Spring code touched.
Show me the 5 most-borrowed books using JPQL. Include title and loan count.
Claude uses jpa/jpa-query to execute JPQL directly via the live
EntityManager and gets back plain Clojure maps — not opaque Java
objects. Scalar projections with AS aliases become named keys.
Lazy associations stay "<lazy>" so no surprise queries fire.
Call the GET /api/books endpoint as a MEMBER and show me what it returns. How large is the response?
lw-call-endpoint invokes the controller method under a live Spring
SecurityContext and serializes the result with the same Jackson
ObjectMapper the HTTP layer uses — output is identical to what a real
HTTP client would receive. The response metadata tells you both the row count and
the raw and gzip-compressed byte size of the full payload.
I suspect .archiveBook is modifying more fields than it should on Book 100. Show me exactly what it changes, but don't actually persist anything.
Claude calls q/diff-entity, which runs the service method inside a
transaction that is always rolled back. It captures the entity
state before and after the call and returns a structured
:changed map — only the keys that differ, with old and new values
side by side. The database is never touched.
Give me a full overview of all entities, their tables, columns, and how they relate to each other. Draw an ASCII ER diagram.
Claude calls intro/inspect-all-entities once to get the
complete Hibernate metamodel — every entity, every column, every relation
— and builds the diagram from that. One round-trip instead of one per entity.
Fix the N+1 on GET /api/books/by-genre/{genreId}. Don't restart the app — hot-swap the query, verify it worked, then write the fix to source.
The payoff. Claude uses hq/hot-swap-query! to patch
findByGenreId live — adding JOIN FETCH for author,
genres, reviews, and reviewer — re-runs trace/trace-sql to
confirm the query count drops from
123 → 1,
writes the fix to BookRepository.java,
then calls hq/reset-all!. Zero restarts.
I want to write an integration test for BookService#getReviewsForBook. The database is empty — can you generate realistic test data and write the test?
Claude runs lw-build-test-recipe to build a full
Review → Book → Author and LibraryMember graph,
captures every generated scalar value (names, rating, comment, dates) into a
recipe map, then prototypes the service call in the REPL to validate the happy
path — before writing a single line of Java. The final test uses the exact
faker-generated values for setup and assertions, so nothing is invented.
I'm about to optimise the query in adminService/getSystemStats. What HTTP endpoints and scheduled jobs call it — directly or via other services?
Claude calls cg/blast-radius, which walks the live bytecode
upward from the target method and cross-references every caller against
registered HTTP handlers and @Scheduled jobs. For this method
it surfaces both GET /api/admin/stats and a nightly
@Scheduled reporter running at 0 0 2 * * * —
a cron job you might never have thought to connect to an
AdminService change. The call-graph index is built once and
cached for the rest of the session.
Are there any controllers that bypass the service layer and wire directly to repositories?
Claude calls lw/all-bean-deps, filters for controllers with
repository dependencies, and surfaces the violation immediately — no source
reading required. In bloated-shelf this reveals
architectureBreakingController injecting bookRepository
directly, bypassing the service layer entirely. The name makes the intent
obvious; the wiring graph makes it provable.
Which public methods on bookService have no known callers? Are any truly dead, or just called internally?
Claude calls cg/dead-methods, which checks every public method
against the full blast-radius index. Results split into :dead
(no callers anywhere — delete candidates) and :internal-only
(called only by sibling methods — public by accident, refactoring candidates).
Automatically warns if messaging beans or db-scheduler tasks are present,
since their callers are not statically visible.
For each method on adminService, which of its injected dependencies does it actually touch? Which deps appear in the most methods?
Claude calls cg/method-dep-map, which walks the bean's bytecode
and returns the exact dependency footprint per method. The :dep-frequency
ranking immediately shows load-bearing deps (used by many methods) vs.
extraction candidates (used by only one). With :intra-calls?
and :callers?, the full internal call graph is visible in one shot
— no source reading required.
Which @Query methods is the query watcher currently tracking? Are any queries hot-swapped right now?
Claude calls qw/status to list every @Query method
the watcher knows about — their current JPQL as it sits on disk — and
hq/list-swapped to check whether any are currently patched.
In bloated-shelf the watcher tracks 7 queries across 4 repositories.
A good orientation call before any hot-swap session: confirms the baseline
and ensures nothing is unexpectedly already swapped.
Once you're comfortable with the basics, the full skill reference covers every function, pitfall, and edge case.
The SKILL.md
you copied into your project is the authoritative
API reference — every namespace, every function signature, known pitfalls,
and worked examples. Read it once; Claude reads it every session.
Livewire gives Claude (and you) full code execution power inside the JVM.
Use anonymized seed data — never point it at a database with
real user data. And keep livewire.enabled out of any profile
that ships to staging or production.