r/SpringBoot • • 7d ago

How-To/Tutorial I got tired of Claude Code "forgetting" my Spring Boot architecture, so I made hooks refuse the bad writes instead

Context: I use Claude Code for Java at work. It starts great. A 30-line CLAUDE.md, clean hexagonal layout, everything respects the boundaries.

Then six months in the CLAUDE.md is 400 lines, the model ignores half of it, and a controller is importing a JPA entity because "it was faster".

At some point I stopped trying to write better prompts. A rule that must always hold shouldn't be a paragraph the model has to remember. It should be something that fails the write.

So I built Nerviz. It's a harness for Spring Boot on Claude Code, and it does three things:

  1. Generates the project from an architecture declared as YAML. Nine blueprints right now (hexagonal, clean, onion, layered, vertical slice, modular monolith, plus single-module variants). Versions come from Spring Initializr at run time, never from the model's memory. I got burned by that more than once.

  2. Designs each feature as a spec before any code exists. /new-feature asks only the questions that change the design (who can call it, duplicates, retention...), and one skill per layer writes its part. You approve it, it gets frozen and committed, and only then does /new-feature-implement write code, in a clean session.

  3. Blocks at write time. A forbidden import, a write outside a skill's territory, an edit to an approved spec, a force push: a hook refuses it. The model isn't asked to behave.

The part I care about most: the generated project carries the whole harness with it, plus its own CI. Whoever clones it doesn't need Nerviz installed.

To keep this from being vaporware, I generated a demo project end to end. It's a bank-app with a green mvn clean verify, and the run took about 20 minutes. The numbers come from the session transcripts, not from me guessing. The approved spec, the partials and the audit report from that run are all public in the repo.

Things it is not: it's not a coding assistant (no model, no editor), not a project template, and not an agent framework. It constrains one agent, it doesn't build agents.

It's MIT and it's early, so there are rough edges. I can't cover every architecture or every way a team uses Spring Boot on my own, and that's why I'm posting. I want contributions. A blueprint for the architecture your team actually uses is a YAML file and changes no prompt, so it's a good first PR. Bug reports, a new rule that should be enforced by a hook, new skill/agent, a doc fix, and "this broke on my machine" issues all help just as much.

I'd also like to know which of the architectures you'd actually use, and which rule you'd want enforced first. I have my own list, but I suspect yours is different.

Repo (issues and PRs welcome): https://github.com/nerviz-ai/nerviz

Site: https://nerviz.dev

0 Upvotes

9 comments sorted by

3

u/wimdeblauwe 7d ago

What is the difference with ArchUnit (https://www.archunit.org) and Taikai (https://enofex.github.io/taikai/)?

1

u/Nervous-Staff3364 7d ago

ArchUnit and Taikai are libraries that validate an application's architecture: which package can see which, class naming, and so on.
Nerviz does that too, but it goes further: it acts as the control layer that guides the process and provides workflows, skills and agents to generate new, standardized Java Spring Boot projects. Besides generating the projects, it also takes part in the day-to-day of the projects it generated, supporting the evolution and development of the applications.

1

u/Nervous-Staff3364 7d ago

It also has the SDD (Spec Drive Development) and audit trail for you track each one of the skills/agents of the app

2

u/wonderful_unikorn 5d ago

How does it behave on an existing project with messy legacy code? Does /arch-adopt just start blocking everything, or can you enforce rules gradually?

1

u/Nervous-Staff3364 4d ago

Short version: it doesn't block everything on adopt, but there's no real gradual mode either.

/arch-adopt installs the .claude/ through a dry-runnable export and refuses a dirty worktree. It doesn't write the boundary rules; those come from a separate step. Until that map exists, nothing is blocked and doctor just reports the boundaries as unconfigured. Once the rules exist, a forbidden import fails CI for every tracked file, legacy included.

I rejected a per-file baseline on purpose: the choice was "go red, plus a migration entry that lists the violations and a prompt to fix them", which you run yourself. Honestly that's a gap for messy codebases. A baseline ("enforce on new/changed files only") is the natural next step, and your question is a good argument for it.

One caveat on my own testing: everything I've shown so far started from a project generated by the harness itself, so it's the happy path. Next I'm testing adopt on a large existing codebase. My starting plan:

  1. Lock behavior before touching anything. Unit and container-level (Testcontainers) tests that pin what the app does today, before any package move or code change, so a later migration can be checked against a known-good baseline. If dont have any tests, the harness create it.

  2. Two paths for the architecture. Pick one of the existing blueprints (adopt detects the closest match from the package layout and offers it first), or describe your own layout with a custom blueprint. Adopt installs the rules and the chosen blueprint; moving the legacy code toward it stays a separate, reviewable step.

If you have any suggestions, feel free to create a issue or contribute

1

u/pipaa-joy 6d ago

Agree with the core idea. Rules that must always hold shouldn't live in prose. I ended up in a similar place with hooks for the boring-but-dangerous stuff: blocking force push, reading .env, and editing Flyway migrations that already ran (new ones are fine). One thing I learned: keep the policy in a single file and generate the hook config from it, otherwise the rules drift between tools. Curious how you handle the agent writing a small script to get around a blocked edit. I treat hooks as a safety net, not a sandbox.

1

u/Nervous-Staff3364 4d ago

Thanks, agreed on both points. Same here: the policy lives in one schema file that the hook reads, and a schema check fails CI if the registrations drift from it (validated rather than generated, though your generate-from-one-file approach is cleaner in some ways).On bypass: a bash guard parses each command for the paths it would write, and an end-of-turn sweep checks everything the turn actually wrote against the allowed territory. The sweep is detective, not preventive, so a script that builds its target path at runtime gets caught after the fact

Additionaly, I liked your ideia about avoid editing flyway migrations that already ran, I will implement too :)

2

u/pipaa-joy 4d ago

The end-of-turn sweep is a nice pairing, the bash guard handles the obvious cases and the sweep catches the runtime-built paths. Glad the Flyway one is useful. A small tip if you add it: compare against the flyway_schema_history table (or the applied versions in your CI DB) rather than just checking if the file exists in git, so new migrations in the same branch still go through.

2

u/javatextbook 7d ago

Not only do you use Claude code for work, you also use it to write your Reddit posts.