How custom detekt rules, specialized AI agents, and specification-driven development keep Clean Architecture intact — even when an LLM writes the code.
Part 4 — | spent 2,000 words establishing.
LLMs are fast. They're also statistically inclined to produce the most common pattern they've seen — and in the Kotlin ecosystem, that's Spring annotations on everything, exceptions for error handling, and no concept of layer boundaries. Your architecture documentation might say "no Spring in application layer." The LLM has seen 100,000 Spring Boot examples that do exactly the opposite.
The three previous articles in this series built an architecture where:
: Domain and application tests run in milliseconds with no Spring context
, every fallible operation returnsEither<XxxError, T>. No exceptions. The return type is the complete error specification.
LLMs don't naturally write this way. They reach for
throw IllegalArgumentException("invalid input")because that's what most Kotlin code does.
CODEclass NoThrowOutsidePresentationRule(config: Config) : Rule(config, "...") {
private val forbiddenLayers = setOf("domain", "application", "infrastructure")
override fun visitThrowExpression(expression: KtThrowExpression) {
super.visitThrowExpression(expression)
val file = expression.containingKtFile
if (file.virtualFilePath.contains("/test/")) return
val packageName = file.packageFqName.asString()
val layer = packageName.removePrefix("$projectBase.").substringBefore(".")
if (layer in forbiddenLayers) {
report(Finding(
Entity.from(expression),
"throw detected in '$packageName'. Use Either<XxxError, T> instead.",
))
}
}
}
Three layers are covered: domain, application, and infrastructure. The only layer where
throwis permitted is presentation — because Spring's exception handler infrastructure (ResponseStatusException,GraphQLException) requires it.
Together, these two rules turn architecture guidelines into compiler-level enforcement. The LLM writes code, the Kotlin compiler checks types, and detekt checks architecture. Violations are build errors, not code review comments.
From documentation to specification: SDD for LLMs
— automated. Tests are written before implementation. Implementation is written to pass the tests. The architecture rules are checked after every cycle. The orchestrator doesn't trust the implementer — it verifies.
Hooks: the last line of defense
Claude Code supports hooks — shell commands that trigger on specific events. Two hooks close the loop between the LLM and the architecture rules:
CODE{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR && ./gradlew ktlintFormat 2>&1 | tail -60",
"timeout": 30
}]
}
],
"Stop": [
{
"hooks": [{
"type": "command",
"command": "cd $CLAUDE_PROJECT_DIR && ./gradlew detekt 2>&1 | tail -60",
"timeout": 120
}]
}
]
}
}
PostToolUse on Write/Edit: Every time the LLM creates or modifies a file, ktlint auto-formats it. No style drift. No formatting discussions. The code matches the project style before the LLM's next turn.
Stop: When the LLM finishes its work, detekt runs automatically. If the custom rules detect a
throwin the domain layer or a Spring import in the application layer, the violation is surfaced immediately. The LLM sees the failure output and can self-correct.
This creates a feedback loop: write → auto-format → continue → finish → architecture check → fix if violated. The LLM operates within the enforcement boundary in real time.
In : The structure — 5 Gradle modules, Arrow-kt Either, value classes, explicit DI. How to build it.
: The proof — swapped the database client, added GraphQL. Domain and application: zero files changed. How to prove the architecture delivers on its promise.
Part 4: The automation — custom detekt rules, specialized agents, SDD pipeline. How to let an LLM build on the architecture without breaking it.
The architecture didn't change between parts. The same module boundaries, the same
Eithererror handling, the same sealed interface hierarchies. What changed was who writes the code — from a human following conventions, to an LLM constrained by static analysis, agent prompts, and automated verification.
Clean Architecture was designed to make software maintainable by humans. It turns out the same constraints — explicit dependencies, typed errors, pure layers — are exactly what LLMs need to write code safely. The architecture didn't change. The developer did.
The full source is on GitHub: |
↗ Original-Artikel auf dev.to lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
SOCIAL SHARE CARD GENERATOR