The first time I pointed Claude Code at a real Clojure codebase, it wrote code that worked.

The tests passed. The feature did what I asked. And then I opened the diff for review, rolled my eyes, and fell back in my chair with a sigh as I readied my hands on the keyboard. The cond had blank lines between every clause. There was a form-2 Reagent component where my whole team has standardized on with-let. A bit of EDN came back over the wire and got handed to clojure.core/read-string — the one that happily evaluates #=(...) and hands an attacker a shell.

None of it was wrong, exactly. It compiled. It ran. But it didn't look like code my team wrote, and a couple of those lines were quietly dangerous. So I spent my review going in and refactoring Claude's work myself, because at least then I'd know what the code looked like when I was done. The last thing I wanted was to get stuck in the cycle of telling Claude to fix it over and over again until it got it right, each prompt getting shorter and less polite as I remembered that Claude isn't a person and doesn't have feelings.

That's the problem. Claude is a capable Clojure developer who had never read my style guide, never sat in my code reviews, and couldn't be trusted to do things a certain way just because I told it to. It learned Clojure from the average of the internet, and that average isn't my team.

So the question I kept coming back to was: how do you onboard it? How do you give Claude the same conventions, instincts, and security reflexes a senior engineer picks up in their first six months on your team — without re-explaining them in every single prompt? And perhaps more importantly, how do you actually enforce it? I can't count the number of times of told Claude, "Hey, I told you to stop doing that!" And Claude responding, "You're right, I'm sorry about that."

The answer turned out to be two Claude Code plugins. This post is a tour of both.


A quick word on what a "plugin" even is

If you've used Claude Code but never installed a plugin, you may not realize how much they can impact your Claude experience. They come in many forms and can provide:

There's no fixed recipe — a plugin includes whichever of those parts it needs, and nothing says it has to have hooks or ship any configuration at all.

The two plugins in this post happen to be built out of three kinds of parts, and it's worth getting those straight up front, because the interplay between them is the whole story of why these plugins work:

Both plugins live in the cleancoders-agent-plugins marketplace. Claude Code doesn't know about a marketplace until you add it, so the first step — once per machine — is to point Claude at it:

/plugin marketplace add cleancoders/agent-plugins

With the marketplace registered, installing either plugin is one line:

/plugin install clojure@cleancoders-agent-plugins
/plugin install clojure-security@cleancoders-agent-plugins

The first teaches Claude to write Clojure like a Clean Coder. The second takes the security steps that a DevSecOps team has in their CI/CD pipeline and executes them during code implementation. Together, they maximize the chances of Claude getting it right the first time.


Part 1: The clojure plugin — clean code on tap

The clojure plugin is the style guide, the senior dev's instincts, and the formatter, all wired into the editing loop. It ships seven teaching skills, a couple of hooks, and a baseline formatter config.

The skills: a playbook for each kind of work

Each skill targets a specific kind of task, and Claude reaches for the right one automatically based on what you've asked it to do:

Skill When it fires What it teaches
writing-clojure-code Any CLJ/CLJC/CLJS you touch Core formatting + idiom: cond/cond->/as-> layout, let-binding alignment, handler short-circuit patterns
writing-reagent-components Editing a Reagent component with-let over form-2, after-render for setup, ccc/for-all, click-handler extraction
using-c3kit-bucket Querying or transacting entities Push filters into queries, db/find-by over mapping db/entity, keep logic in CLJC
using-forms Building a form forms/config, field-set, submit-button, server-side validation errors done right
writing-migrations Adding a DB migration Naming, attribute ordering, Datomic's immutable types, migration-scoped DB
writing-tests Writing Speclj specs One describe per file, clean test output, autorunner recovery
creating-pages New SPA route Namespace, frontend and backend routes, main.cljs registration

Here's what makes these more than a generic Clojure style guide: they're tailored to the c3kit suite — the family of libraries we build on at Clean Coders. Claude doesn't just learn "good Clojure" in the abstract; it learns how to use bucket for persistence, apron for the common utilities, and wire for the client/server plumbing and JavaScript interop, the way they're actually meant to be used. That's a big deal, because these are exactly the libraries an LLM has seen the least of in training. Left to its own devices, Claude will cheerfully hand-roll a query that bucket already does better, or filter a result set in Clojure that it should have filtered in the database.

It matters most in three places:

If you're interested in learning more about writing good, well-tested migrations with c3kit-bucket, checkout our blog post on it here.

The thing I want to highlight isn't any single skill — it's that this is the exact knowledge a new hire absorbs over months, written down once. Take writing-clojure-code. Among other things, it encodes a rule my team cares about a lot:

A cond has each test and its result on one line, results column-aligned, and never a blank line between clauses. A blank gutter is a smell — it means a result got too big and should be extracted into a helper.

So instead of this, which is what you tend to get out of the box:

(cond
  weekly?
  [badge/badge-container default-badges]

  @frozen?
  nil

  (not-empty badges)
  [badge/badge-container badges])

Claude now writes this:

(cond
  weekly?            [badge/badge-container default-badges]
  @frozen?           nil
  (not-empty badges) [badge/badge-container badges])

That's a small example, but multiply it across cond->, let alignment, Reagent's with-let-not-form-2 rule, the "push your filters into the db/find-by instead of filter-ing in Clojure" rule, the "one describe per file" rule — and the diffs stop looking like a stranger wrote them. They look like your team wrote them.

The hook: formatting stops being a conversation

Skills are advice, and advice can be forgotten. So the plugin doesn't only rely on Claude remembering to format well — it ships a PostToolUse hook that runs cljfmt fix on every .clj, .cljs, .cljc, .edn, or .bb file the moment Claude edits it.

If the formatter rewrites a file, the hook tells Claude exactly which paths changed so it re-reads them before its next edit. No drift, no "please run the formatter" in your prompts, no formatting noise in the diff for you to wade through. The formatting argument is simply over.

The pattern worth stealing here: the hook does its work transparently — it fixes the code in place and notifies Claude — rather than nagging Claude to do it. That's the difference between a linter that complains and a teammate who just quietly keeps the kitchen clean.

And because formatting only matters if everyone agrees on the rules, the plugin ships the cljfmt.edn it formats against — pre-loaded with the right indentation for Speclj's describe/context/it, c3kit's for-all, and Reagent's with-let. Run /clojure:setup-cljfmt and it drops the config into your project (showing you a diff first if you already have one). Now your formatter, your CI, and Claude all agree.

There's also a small SessionStart hook that, in a Clojure project, checks whether cljfmt is even installed and gives you a heads-up with an install hint if it's missing — so the formatting loop doesn't silently no-op.


Part 2: The clojure-security plugin — a security reviewer that never gets tired

Clean code you can catch in review. Security bugs are different — they hide. A read-string looks exactly like an edn/read-string. A string-concatenated query looks like every other query until someone types '; DROP TABLE into a form field. And the standard advice — "run a SAST tool in CI" — has a real gap: by the time CI flags it, Claude has already moved on, the context is gone, and you're triaging a wall of findings days later.

That gap is really a process problem, and it's the one a secure software development lifecycle (SSDLC) exists to solve. The whole premise of SSDLC — and of DevSecOps, its CI/CD-flavored cousin — is that security isn't a gate you bolt on at the end; it's a thread that runs through every stage of development. We take that seriously at Clean Coders, and it's exactly the principle this plugin applies to working with an AI. If the clojure plugin makes sure Claude writes good code from the first keystroke, the clojure-security plugin makes sure it writes security-minded code from the first keystroke — woven into the act of writing, not audited as a separate step once the code already exists.

The way it does that is by shifting security feedback left — all the way into the editing loop, while Claude is still holding the context that produced the code — and, just as importantly, by teaching Claude the judgment that mechanical tools don't have.

The flyover: what's in the box

Before we get into the weeds, here's the shape of the plugin. It's three moving parts, mapped right onto the skills/hooks/config model from earlier:

Underneath all three sits a roster of real security tools — clj-kondo, clj-holmes, gitleaks, clj-watson, and Semgrep — that the plugin drives and, crucially, triages on your behalf. We'll come back to those. Let's start with the judgment, because it's what makes the rest more than a noisy wrapper around a pile of scanners.

The judgment layer: 12 vulnerability classes Claude now recognizes

This is the heart of it. The plugin's main skill is a reference for twelve classes of Clojure-specific vulnerability, each with the patterns to grep for, the safe alternative, and — critically — the false-positive traps. A few highlights:

Why does encoding this matter? Because the tools alone don't get you there. Each one is blind to something:

Tool Catches Blind to
clj-kondo Unresolved syms, arity, reflection Semantics, taint, deserialization, SQLi
clj-holmes Known-bad Clojure idioms Dataflow
clj-watson Transitive dep CVEs Source-code bugs
gitleaks Secrets by regex/entropy Custom-encoded secrets
Semgrep Pattern matches Deep dataflow

Every one of those tools matches patterns. None of them can trace a tainted value from an HTTP handler, across three function calls, to the read-string that detonates it — or, just as importantly, tell you that this particular read-string is fine because its argument is a build-time constant. That tracing is judgment, and judgment is exactly what the skill hands Claude:

For every candidate finding, walk it back: Where did the tainted value originate? What trust boundary does it cross? Is there already sanitization on the path? Would removing the call break a legitimate use? Severity is reachability × impact — an unauthenticated network path outranks an internal batch job, every time.

That last bit is what keeps the plugin from being a fire hose of noise. It tells Claude to downgrade a finding only reachable on an internal network, to mark something provisional when it can't prove the provenance, and to leave an inline annotation when a call is intentionally safe:

; clojure-security: read-string OK — constant from build config

That's the behavior of a thoughtful security reviewer, not a script.

The hooks: defense in depth around the editing loop

The judgment lives in the skill. The enforcement lives in a stack of five hooks, each at a different checkpoint:

Notice the shape: lint on every edit, scan before finishing a turn, block before a commit. The same finding gets three chances to be caught, each one cheaper to fix than the last. That's defense in depth, applied to an AI's workflow.

And here's the part that keeps all of this from being a burden: the hooks only run what you actually have installed. No clj-holmes on your PATH? The scan that depends on it quietly no-ops — it doesn't error, doesn't block your turn, doesn't nag. Real failures still surface; a missing tool just means that particular check sits out. So you can install the plugin today and adopt the toolchain at your own pace, one tool at a time, instead of being locked out until you've set up all five.

It even smooths over the one piece of setup that usually trips people up. clj-holmes needs a rules directory to do anything, and fetching it is the step everyone forgets. So if you have clj-holmes installed but its rules aren't on disk yet, the hooks fetch them for you automatically the first time they need them. Install the binary; the plugin handles the rest.

The on-demand audit

Sometimes you don't want the per-edit dribble — you want a full sweep. That's the /security-audit command:

/security-audit              # whole repo
/security-audit staged       # just what's staged
/security-audit src/clj/app  # a subscope

It runs the pattern sweep, invokes whichever of clj-kondo / clj-holmes / gitleaks / clj-watson / Semgrep you have installed, triages every candidate against those twelve vulnerability classes, and hands back a report bucketed by severity — path:line [class-name] with a one-line problem and a fix direction. It never auto-fixes. Security fixes are a human call; the plugin's job is to surface the finding with the reasoning and let you decide.

One config detail I have to call out

The plugin ships a security-tuned .clj-kondo/config.edn (run /clojure-security:setup-clj-kondo to install it) that escalates :type-mismatch and :refer-all to hard errors — because type confusion is a real correctness-and-security defect and :refer :all hides where a symbol came from.

But escalating :type-mismatch to an error has a nasty side effect in a Speclj codebase: Speclj's with macro declares a lazily-evaluated, deref-able binding, and clj-kondo doesn't know that, so every single @my-binding gets flagged as a type mismatch. In one real project that was 468 false errors. So the plugin also ships a tiny clj-kondo hook that rewrites with/with-all at analysis time so the deref type-checks correctly. The setup skill always copies that hook in. It's a small thing, but it's the kind of small thing that's the difference between a config you keep and a config you rage-quit on day one.


So what does it actually feel like?

Put both plugins on and the experience shifts in a way that's hard to overstate. You ask Claude to add a feature. It writes a migration using the dated-namespace pattern and a migration-scoped DB — because writing-migrations told it to. It builds the form with forms/field-set and returns inline validation errors the right way — because using-forms told it to. The cljfmt hook formats everything as it goes, so the diff is clean. When it reaches for read-string on a request body, the security skill steers it to edn/read-string, and if it somehow doesn't, the Stop hook catches it before Claude can hand the turn back to you.

You review a diff that already looks like your team wrote it, with the dangerous patterns already filtered out. Instead of telling Claude everything it did wrong, you're telling it to teardown the new feature's work tree and merge to main.

So here's the mental model one more time: skills give Claude the knowledge a senior teammate has. Hooks give it the reflexes — the things a good developer does without thinking. And the shared configs make sure Claude, your CI, and your humans are all grading against the same rubric. Knowledge plus reflexes plus a shared standard. That's onboarding.

That's the whole idea. Claude was already a strong Clojure developer. These plugins give it your team's taste and a security conscience — so the code it writes is code you'd be proud to ship.


What about Codex, or whatever you're running?

I've written this whole post around Claude Code, and that's no accident: these plugins were purpose-built for it. The /plugin install flow, the skills that auto-load when they're relevant, the hooks that fire on edits and turn-end — those are all Claude Code mechanisms, and we leaned into them deliberately. But Claude Code is just one harness, and the AI tooling world is a zoo right now: Codex, OpenCode, Cursor, Gemini CLI, and a new one seemingly every week. So the fair question is whether any of this is useful if you're not on Claude.

Honest answer: partly today, fully with a little wiring. It helps to split the plugins back into the three pieces from earlier, because they port very differently.

So the short version: the standards these plugins encode — the formatting, the lint rules, the vulnerability classes, the security tooling — are completely portable, because they live in tool configs and plain Markdown. The automation that makes them effortless is what's bound to Claude Code, and even that is mostly a question of where you hang a few bash scripts. If you're on another agent today, lift the configs, feed it the skills, and put the security tools in your git hooks — you'll be most of the way there.


Try it

If you're using Claude Code on a Clojure or ClojureScript project, add the marketplace once and grab both plugins:

/plugin marketplace add cleancoders/agent-plugins
/plugin install clojure@cleancoders-agent-plugins
/plugin install clojure-security@cleancoders-agent-plugins

Then run /clojure:setup-cljfmt and /clojure-security:setup-clj-kondo to drop the matching configs into your repo, and you're off. Ask Claude to build something, and watch the diffs come back clean.

If you're starting a project from scratch, these plugins are the perfect companion to c3kit-jig, our recently released CLI for scaffolding full-stack Clojure apps (I wrote about it here). jig bootstraps a fresh app on the exact c3kit stack — bucket, wire, Scaffold, Reagent — that these skills are built to teach. So jig lays the foundation the c3kit way, and the plugins keep Claude building on it the c3kit way. Scaffold the app, install both plugins, and Claude is writing idiomatic, security-minded code against your new project from the very first feature.

The conventions baked into these plugins are the ones we live by at Clean Coders, but they're all plain, readable skill files — fork them, tune them to your team's taste, and make Claude write Clojure the way you write Clojure.

Happy hacking!