Add backpressure stack for agentic coding quality gates

PostToolUse hooks run after every file edit:
- Backend: ./mvnw compile (Checkstyle Google Style + javac)
- Frontend: vue-tsc --noEmit + oxlint + ESLint

Stop hook runs test suites when source files changed, blocks the
agent on failure and re-engages it to fix the issue. Output is
filtered to [ERROR] lines only for context efficiency.

Static analysis: Checkstyle (validate phase), SpotBugs (verify phase),
ArchUnit (9 hexagonal architecture rules as JUnit tests).

Fail-fast: Surefire skipAfterFailureCount=1, Vitest bail=1.
Test log noise suppressed via logback-test.xml (WARN level),
redirectTestOutputToFile, and trimStackTrace.

Existing Java sources reformatted to Google Style (2-space indent,
import order, Javadoc on public types).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 02:44:15 +01:00
parent a55174b323
commit a9802c2881
15 changed files with 1098 additions and 43 deletions

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
# Read hook input from stdin (JSON with tool_input.file_path)
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))" 2>/dev/null || echo "")
# Only run for Java files under backend/
case "$FILE_PATH" in
*/backend/src/*.java|backend/src/*.java) ;;
*) exit 0 ;;
esac
cd "$CLAUDE_PROJECT_DIR/backend"
# Run compile (includes validate phase -> Checkstyle if configured)
# Context-efficient: suppress output on success, show full output on failure
if OUTPUT=$(./mvnw compile -q 2>&1); then
echo '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"✓ Backend compile passed."}}'
else
ESCAPED=$(echo "$OUTPUT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":$ESCAPED}}"
fi

37
.claude/hooks/frontend-check.sh Executable file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
# Read hook input from stdin (JSON with tool_input.file_path)
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))" 2>/dev/null || echo "")
# Only run for TS/Vue files under frontend/
case "$FILE_PATH" in
*/frontend/src/*.ts|*/frontend/src/*.vue|frontend/src/*.ts|frontend/src/*.vue) ;;
*) exit 0 ;;
esac
cd "$CLAUDE_PROJECT_DIR/frontend"
ERRORS=""
# Type-check
if OUTPUT=$(npx vue-tsc --noEmit 2>&1); then
:
else
ERRORS+="Type-check failed:\n$OUTPUT\n\n"
fi
# Lint (without --fix — agent must self-correct)
if OUTPUT=$(npx oxlint . 2>&1 && npx eslint . --cache 2>&1); then
:
else
ERRORS+="Lint failed:\n$OUTPUT\n\n"
fi
if [[ -n "$ERRORS" ]]; then
ESCAPED=$(printf '%s' "$ERRORS" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":$ESCAPED}}"
else
echo '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"✓ Frontend type-check + lint passed."}}'
fi

54
.claude/hooks/run-tests.sh Executable file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$CLAUDE_PROJECT_DIR"
# Read hook input from stdin
INPUT=$(cat)
# Prevent infinite loops: if already re-engaged by a previous Stop hook, let it stop
STOP_HOOK_ACTIVE=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('stop_hook_active', False))" 2>/dev/null || echo "False")
if [[ "$STOP_HOOK_ACTIVE" == "True" ]]; then
exit 0
fi
# Check for uncommitted changes in backend/frontend source
HAS_BACKEND=$(git status --porcelain backend/src/ 2>/dev/null | head -1)
HAS_FRONTEND=$(git status --porcelain frontend/src/ 2>/dev/null | head -1)
# Nothing changed -- skip
if [[ -z "$HAS_BACKEND" && -z "$HAS_FRONTEND" ]]; then
exit 0
fi
ERRORS=""
PASSED=""
# Run backend tests if Java sources changed
if [[ -n "$HAS_BACKEND" ]]; then
if OUTPUT=$(cd backend && ./mvnw test -q 2>&1); then
PASSED+="✓ Backend tests passed. "
else
# Filter: only [ERROR] lines, skip Maven boilerplate
FILTERED=$(echo "$OUTPUT" | grep -E "^\[ERROR\]" | grep -v -E "Re-run Maven|See |Help 1|full stack trace|Failed to execute goal|For more information|^\[ERROR\] *$" || true)
ERRORS+="Backend tests failed:\n$FILTERED\n\n"
fi
fi
# Run frontend tests if TS/Vue sources changed
if [[ -n "$HAS_FRONTEND" ]]; then
if OUTPUT=$(cd frontend && npm run test:unit -- --run 2>&1); then
PASSED+="✓ Frontend tests passed. "
else
ERRORS+="Frontend tests failed:\n$OUTPUT\n\n"
fi
fi
if [[ -n "$ERRORS" ]]; then
# Block stopping — re-engage the agent to fix failures
ESCAPED=$(printf '%s' "$ERRORS" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
echo "{\"decision\":\"block\",\"reason\":$ESCAPED}"
else
# Success — allow stopping, report via stopReason
echo "{\"decision\":\"approve\",\"reason\":\"$PASSED\"}"
fi

32
.claude/settings.json Normal file
View File

@@ -0,0 +1,32 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/backend-compile-check.sh\"",
"timeout": 120
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/frontend-check.sh\"",
"timeout": 120
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/run-tests.sh\"",
"timeout": 300
}
]
}
]
}
}

View File

@@ -67,13 +67,15 @@ These are the non-negotiable principles of this project. Every decision — arch
### Build Commands
| What | Command |
|------|---------|
|------------------|----------------------------------------|
| Backend test | `cd backend && ./mvnw test` |
| Backend run | `cd backend && ./mvnw spring-boot:run` |
| Frontend install | `cd frontend && npm install` |
| Frontend build | `cd frontend && npm run build` |
| Frontend test | `cd frontend && npm run test:unit` |
| Frontend dev | `cd frontend && npm run dev` |
| Backend checkstyle | `cd backend && ./mvnw checkstyle:check` |
| Backend full verify | `cd backend && ./mvnw verify` |
### Agent Documentation
@@ -82,6 +84,17 @@ These are the non-negotiable principles of this project. Every decision — arch
- Agent test reports (browser verification): `.agent-tests/` (gitignored)
- Use the `browser-interactive-testing` skill (rodney/showboat) for visual verification — this is an agent tool, not manual work.
### Skills
The following skills are available and should be used for their respective purposes:
| Skill | Trigger | Purpose |
|-------------------------------|----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
| `rpi-research` | Deep codebase investigation needed | Conducts thorough research and produces a written report. Use for understanding how something works before changing it. |
| `rpi-plan` | Feature, refactor, or bug fix needs planning | Creates detailed, phased implementation plans through interactive research and iteration. |
| `rpi-implement` | Approved plan ready for execution | Executes approved implementation plans phase by phase with automated and manual verification. |
| `browser-interactive-testing` | Visual verification of web pages | Headless Chrome testing via rodney/showboat. Use for screenshots, browser automation, and visual test reports. |
### Ralph Loops
- Autonomous work is done via Ralph Loops. See [.claude/rules/ralph-loops.md](.claude/rules/ralph-loops.md) for documentation.

View File

@@ -34,7 +34,7 @@ A privacy-focused, self-hostable web app for event announcements and RSVPs. An a
## Tech stack
| Layer | Technology |
|--------------|------------------------------------------|
|--------------|--------------------------------------------|
| Backend | Java (latest LTS), Spring Boot, Maven |
| Frontend | Vue 3, Vite, TypeScript |
| Database | PostgreSQL (external, not bundled) |
@@ -90,7 +90,7 @@ The app runs at `http://localhost:8080`. Database migrations run automatically o
All configuration is done via environment variables:
| Variable | Required | Description |
|----------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------|
|---------------------|----------|-----------------------------------------------------------------------------------------------------------------------------|
| `DATABASE_URL` | Yes | JDBC connection string for PostgreSQL |
| `MAX_ACTIVE_EVENTS` | No | Maximum number of non-expired events. Unset = unlimited |
| `UNSPLASH_API_KEY` | No | Enables header image search via Unsplash. Images are fetched server-side and stored locally — guests never contact Unsplash |
@@ -138,6 +138,57 @@ cd backend && mvn package
cd frontend && npm run build
```
## Code quality
Automated quality gates run as Claude Code hooks during AI-assisted development. They provide immediate feedback after every file edit and block the agent from finishing when tests fail.
### Backend (Java / Maven)
**After editing a `*.java` file** (PostToolUse hook):
| What | Command | Fails on |
|---------------------|------------------|----------------------------------|
| Checkstyle | `./mvnw compile` | Style violations (Google Style) |
| Java compiler | `./mvnw compile` | Compile errors |
Checkstyle enforces Google Style (2-space indent, import order, Javadoc on public types) and is bound to the `validate` phase, so it runs automatically as part of every `compile`. Covers both `src/main` and `src/test`.
**When the agent finishes** (Stop hook — only if `backend/src/` has uncommitted changes):
| What | Command | Fails on |
|---------------------|----------------|---------------------------------------|
| JUnit 5 | `./mvnw test` | Test failures (fail-fast, stops at 1) |
| ArchUnit (9 rules) | `./mvnw test` | Hexagonal architecture violations |
ArchUnit enforces hexagonal boundaries: domain must not depend on adapters, application, config, or Spring; ports must be interfaces; web and persistence adapters must not cross-depend.
**Not hooked** (run manually):
| What | Command | Fails on |
|---------------------|------------------|----------------------------------------------------|
| SpotBugs | `./mvnw verify` | Potential bugs, null dereferences, resource leaks |
### Frontend (TypeScript / Vue)
**After editing a `*.ts` or `*.vue` file** (PostToolUse hook):
| What | Command | Fails on |
|---------------------|--------------------|-----------------|
| TypeScript (strict) | `vue-tsc --noEmit` | Type errors |
| oxlint + ESLint | `oxlint`, `eslint` | Lint violations |
**When the agent finishes** (Stop hook — only if `frontend/src/` has uncommitted changes):
| What | Command | Fails on |
|---------------------|------------------------------|---------------------------------------|
| Vitest | `npm run test:unit -- --run` | Test failures (fail-fast, stops at 1) |
**Not hooked** (run manually or via editor):
| What | Command | Fails on |
|---------------------|------------------|-------------------|
| Prettier | `npm run format` | Formatting issues |
## License
GPL — see [LICENSE](LICENSE) for details.

View File

@@ -33,10 +33,73 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.4.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>13.3.0</version>
</dependency>
</dependencies>
<configuration>
<configLocation>google_checks.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failOnViolation>true</failOnViolation>
<violationSeverity>warning</violationSeverity>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
</configuration>
<executions>
<execution>
<id>checkstyle-validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- Fail-fast: stop on first test failure -->
<skipAfterFailureCount>1</skipAfterFailureCount>
<!-- Context-efficient output for agent backpressure -->
<trimStackTrace>true</trimStackTrace>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.9.8.2</version>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<xmlOutput>true</xmlOutput>
<failOnError>true</failOnError>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>

View File

@@ -3,9 +3,11 @@ package de.fete;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/** Spring Boot entry point for the fete application. */
@SpringBootApplication
public class FeteApplication {
/** Starts the application. */
public static void main(String[] args) {
SpringApplication.run(FeteApplication.class, args);
}

View File

@@ -1,13 +1,14 @@
package de.fete.adapter.in.web;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/** REST endpoint for health checks. */
@RestController
public class HealthController {
/** Returns a simple health status. */
@GetMapping("/health")
public Map<String, String> health() {
return Map.of("status", "ok");

View File

@@ -1,15 +1,15 @@
package de.fete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class FeteApplicationTest {

View File

@@ -0,0 +1,63 @@
package de.fete;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.library.Architectures.onionArchitecture;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
@AnalyzeClasses(packages = "de.fete", importOptions = ImportOption.DoNotIncludeTests.class)
class HexagonalArchitectureTest {
@ArchTest
static final ArchRule onionArchitectureIsRespected = onionArchitecture()
.domainModels("de.fete.domain.model..")
.domainServices("de.fete.domain.port.in..", "de.fete.domain.port.out..")
.applicationServices("de.fete.application.service..")
.adapter("web", "de.fete.adapter.in.web..")
.adapter("persistence", "de.fete.adapter.out.persistence..")
.adapter("config", "de.fete.config..");
@ArchTest
static final ArchRule domainMustNotDependOnAdapters = noClasses()
.that().resideInAPackage("de.fete.domain..")
.should().dependOnClassesThat().resideInAPackage("de.fete.adapter..");
@ArchTest
static final ArchRule domainMustNotDependOnApplication = noClasses()
.that().resideInAPackage("de.fete.domain..")
.should().dependOnClassesThat().resideInAPackage("de.fete.application..");
@ArchTest
static final ArchRule domainMustNotDependOnConfig = noClasses()
.that().resideInAPackage("de.fete.domain..")
.should().dependOnClassesThat().resideInAPackage("de.fete.config..");
@ArchTest
static final ArchRule inboundPortsMustBeInterfaces = classes()
.that().resideInAPackage("de.fete.domain.port.in..")
.should().beInterfaces();
@ArchTest
static final ArchRule outboundPortsMustBeInterfaces = classes()
.that().resideInAPackage("de.fete.domain.port.out..")
.should().beInterfaces();
@ArchTest
static final ArchRule domainMustNotUseSpring = noClasses()
.that().resideInAPackage("de.fete.domain..")
.should().dependOnClassesThat().resideInAPackage("org.springframework..");
@ArchTest
static final ArchRule webMustNotDependOnPersistence = noClasses()
.that().resideInAPackage("de.fete.adapter.in.web..")
.should().dependOnClassesThat().resideInAPackage("de.fete.adapter.out.persistence..");
@ArchTest
static final ArchRule persistenceMustNotDependOnWeb = noClasses()
.that().resideInAPackage("de.fete.adapter.out.persistence..")
.should().dependOnClassesThat().resideInAPackage("de.fete.adapter.in.web..");
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

View File

@@ -0,0 +1,487 @@
---
date: 2026-03-04T01:40:21+01:00
git_commit: a55174b32333d0f46a55d94a50604344d1ba33f6
branch: master
topic: "Backpressure for Agentic Coding"
tags: [plan, backpressure, hooks, checkstyle, spotbugs, archunit, quality]
status: complete
---
# Backpressure for Agentic Coding — Implementation Plan
## Overview
Implement automated feedback mechanisms (backpressure) that force the AI agent to self-correct before a human reviews the output. The approach follows the 90/10 rule: 90% deterministic constraints (types, linting, architecture tests), 10% agentic review.
## Current State vs. Desired State
| Layer | Backend (now) | Backend (after) | Frontend (now) | Frontend (after) |
|-------|---------------|-----------------|----------------|------------------|
| Type System | Java 25 (strong) | *unchanged* | TS strict + `noUncheckedIndexedAccess` | *unchanged* |
| Static Analysis | **None** | Checkstyle (Google Style) + SpotBugs | ESLint + oxlint + Prettier | *unchanged* |
| Architecture Tests | **None** | ArchUnit (hexagonal enforcement) | N/A | N/A |
| Unit Tests | JUnit 5 | JUnit 5 + fail-fast (`skipAfterFailureCount: 1`) | Vitest | Vitest + fail-fast (`bail: 1`) |
| PostToolUse Hook | **None** | `./mvnw compile -q` (incl. Checkstyle) | **None** | `vue-tsc --noEmit` |
| Stop Hook | **None** | `./mvnw test` | **None** | `npm run test:unit -- --run` |
## What We're NOT Doing
- **Error Prone** — overlaps with SpotBugs, Java 25 compatibility uncertain, more invasive setup
- **Custom ESLint rules** — add later when recurring agent mistakes are observed
- **MCP LSP Server** — experimental, high setup cost, unclear benefit vs. hooks
- **Pre-commit git hooks** — orthogonal concern, not part of this plan
- **CI/CD pipeline** — out of scope, this is about local agent feedback
## Design Decisions
- **Hook matchers** are regex on **tool names** (not file paths). File-path filtering must happen inside the hook script via `tool_input.file_path` from stdin JSON.
- **Context-efficient output**: ✓ on success, full error on failure. Don't waste the agent's context window with passing output.
- **Fail-fast**: one failure at a time. Prevents context-switching between multiple bugs.
- **Stop hook** checks `git status --porcelain` to determine if code files changed. Skips test runs on conversational responses.
- **Checkstyle** bound to Maven `validate` phase — automatically triggered by `./mvnw compile`, which means the PostToolUse hook gets Checkstyle for free.
- **SpotBugs** bound to Maven `verify` phase — NOT hooked, run manually via `./mvnw verify`.
- **ArchUnit**: use `archunit-junit5` 1.4.1 only. Do NOT use `archunit-hexagonal` addon (dormant since Jul 2023, pulls Kotlin, pinned to ArchUnit 1.0.1, expects different package naming).
---
## Task List
Tasks are ordered by priority. Execute one task per iteration, in order. Each task includes the exact changes and a verification command.
### T-BP-01: Create backend compile-check hook script `[x]`
**File**: `.claude/hooks/backend-compile-check.sh` (new, create `.claude/hooks/` directory if needed)
```bash
#!/usr/bin/env bash
set -euo pipefail
# Read hook input from stdin (JSON with tool_input.file_path)
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))" 2>/dev/null || echo "")
# Only run for Java files under backend/
case "$FILE_PATH" in
*/backend/src/*.java) ;;
*) exit 0 ;;
esac
cd "$CLAUDE_PROJECT_DIR/backend"
# Run compile (includes validate phase → Checkstyle if configured)
# Context-efficient: suppress output on success, show full output on failure
if OUTPUT=$(./mvnw compile -q 2>&1); then
echo '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"✓ Backend compile passed."}}'
else
ESCAPED=$(echo "$OUTPUT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":$ESCAPED}}"
fi
```
Make executable: `chmod +x .claude/hooks/backend-compile-check.sh`
**Verify**: `echo '{"tool_input":{"file_path":"backend/src/main/java/de/fete/FeteApplication.java"}}' | CLAUDE_PROJECT_DIR=. .claude/hooks/backend-compile-check.sh` → should output JSON with "✓ Backend compile passed."
**Verify skip**: `echo '{"tool_input":{"file_path":"README.md"}}' | CLAUDE_PROJECT_DIR=. .claude/hooks/backend-compile-check.sh` → should exit 0 silently.
---
### T-BP-02: Create frontend type-check hook script `[x]`
**File**: `.claude/hooks/frontend-type-check.sh` (new)
```bash
#!/usr/bin/env bash
set -euo pipefail
# Read hook input from stdin (JSON with tool_input.file_path)
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))" 2>/dev/null || echo "")
# Only run for TS/Vue files under frontend/
case "$FILE_PATH" in
*/frontend/src/*.ts|*/frontend/src/*.vue) ;;
*) exit 0 ;;
esac
cd "$CLAUDE_PROJECT_DIR/frontend"
# Run type-check
# Context-efficient: suppress output on success, show full output on failure
if OUTPUT=$(npx vue-tsc --noEmit 2>&1); then
echo '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"✓ Frontend type-check passed."}}'
else
ESCAPED=$(echo "$OUTPUT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":$ESCAPED}}"
fi
```
Make executable: `chmod +x .claude/hooks/frontend-type-check.sh`
**Verify**: `echo '{"tool_input":{"file_path":"frontend/src/App.vue"}}' | CLAUDE_PROJECT_DIR=. .claude/hooks/frontend-type-check.sh` → should output JSON with "✓ Frontend type-check passed."
---
### T-BP-03: Create stop hook script (test gate) `[x]`
**File**: `.claude/hooks/run-tests.sh` (new)
Runs after the agent finishes its response. Checks `git status` for changed source files and runs the relevant test suites. Skips on conversational responses (no code changes).
```bash
#!/usr/bin/env bash
set -euo pipefail
cd "$CLAUDE_PROJECT_DIR"
# Check for uncommitted changes in backend/frontend source
HAS_BACKEND=$(git status --porcelain backend/src/ 2>/dev/null | head -1)
HAS_FRONTEND=$(git status --porcelain frontend/src/ 2>/dev/null | head -1)
# Nothing changed — skip
if [[ -z "$HAS_BACKEND" && -z "$HAS_FRONTEND" ]]; then
exit 0
fi
ERRORS=""
PASSED=""
# Run backend tests if Java sources changed
if [[ -n "$HAS_BACKEND" ]]; then
if OUTPUT=$(cd backend && ./mvnw test -q 2>&1); then
PASSED+="✓ Backend tests passed. "
else
ERRORS+="Backend tests failed:\n$OUTPUT\n\n"
fi
fi
# Run frontend tests if TS/Vue sources changed
if [[ -n "$HAS_FRONTEND" ]]; then
if OUTPUT=$(cd frontend && npm run test:unit -- --run 2>&1); then
PASSED+="✓ Frontend tests passed. "
else
ERRORS+="Frontend tests failed:\n$OUTPUT\n\n"
fi
fi
if [[ -n "$ERRORS" ]]; then
ESCAPED=$(printf '%s' "$ERRORS" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"Stop\",\"additionalContext\":$ESCAPED}}"
else
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"Stop\",\"additionalContext\":\"$PASSED\"}}"
fi
```
Make executable: `chmod +x .claude/hooks/run-tests.sh`
**Verify**: `CLAUDE_PROJECT_DIR=. .claude/hooks/run-tests.sh` → if no uncommitted changes in source dirs, should exit 0 silently.
---
### T-BP-04: Create `.claude/settings.json` with hook configuration `[x]`
**File**: `.claude/settings.json` (new — do NOT modify `.claude/settings.local.json`, that has permissions)
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/backend-compile-check.sh\"",
"timeout": 120
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/frontend-type-check.sh\"",
"timeout": 60
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/run-tests.sh\"",
"timeout": 300
}
]
}
]
}
}
```
**Verify**: File exists and is valid JSON: `python3 -c "import json; json.load(open('.claude/settings.json'))"`
---
### T-BP-05: Configure Vitest fail-fast `[x]`
**File**: `frontend/vitest.config.ts` (modify)
Add `bail: 1` to the test configuration object. The result should look like:
```typescript
export default mergeConfig(
viteConfig,
defineConfig({
test: {
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'e2e/**'],
root: fileURLToPath(new URL('./', import.meta.url)),
bail: 1,
},
}),
)
```
**Verify**: `cd frontend && npm run test:unit -- --run` passes.
---
### T-BP-06: Configure Maven Surefire fail-fast `[x]`
**File**: `backend/pom.xml` (modify)
Add `maven-surefire-plugin` configuration within `<build><plugins>`:
```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- Fail-fast: stop on first test failure -->
<skipAfterFailureCount>1</skipAfterFailureCount>
</configuration>
</plugin>
```
**Verify**: `cd backend && ./mvnw test` passes.
---
### T-BP-07: Add Checkstyle plugin + fix violations `[x]`
**File**: `backend/pom.xml` (modify)
Add `maven-checkstyle-plugin` within `<build><plugins>`:
```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>13.3.0</version>
</dependency>
</dependencies>
<configuration>
<configLocation>google_checks.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failOnViolation>true</failOnViolation>
<violationSeverity>warning</violationSeverity>
</configuration>
<executions>
<execution>
<id>checkstyle-validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
```
Then run `cd backend && ./mvnw checkstyle:check` to find violations. Fix all violations in existing source files (`FeteApplication.java`, `HealthController.java`, `FeteApplicationTest.java`, all `package-info.java` files). Google Style requires: 2-space indentation, specific import order, Javadoc on public types, max line length 100.
**Verify**: `cd backend && ./mvnw checkstyle:check` passes with zero violations AND `cd backend && ./mvnw compile` passes (Checkstyle now runs during validate phase).
---
### T-BP-08: Add SpotBugs plugin + verify `[x]`
**File**: `backend/pom.xml` (modify)
Add `spotbugs-maven-plugin` within `<build><plugins>`:
```xml
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.9.8.2</version>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<xmlOutput>true</xmlOutput>
<failOnError>true</failOnError>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
```
Run `cd backend && ./mvnw verify` — if SpotBugs finds issues, fix them.
**Verify**: `cd backend && ./mvnw verify` passes (includes compile + test + SpotBugs).
---
### T-BP-09: Add ArchUnit dependency + write architecture tests `[x]`
**File 1**: `backend/pom.xml` (modify) — add within `<dependencies>`:
```xml
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.4.1</version>
<scope>test</scope>
</dependency>
```
Do NOT use the `archunit-hexagonal` addon.
**File 2**: `backend/src/test/java/de/fete/HexagonalArchitectureTest.java` (new)
```java
package de.fete;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.library.Architectures.onionArchitecture;
@AnalyzeClasses(packages = "de.fete", importOptions = ImportOption.DoNotIncludeTests.class)
class HexagonalArchitectureTest {
@ArchTest
static final ArchRule onion_architecture_is_respected = onionArchitecture()
.domainModels("de.fete.domain.model..")
.domainServices("de.fete.domain.port.in..", "de.fete.domain.port.out..")
.applicationServices("de.fete.application.service..")
.adapter("web", "de.fete.adapter.in.web..")
.adapter("persistence", "de.fete.adapter.out.persistence..")
.adapter("config", "de.fete.config..");
@ArchTest
static final ArchRule domain_must_not_depend_on_adapters = noClasses()
.that().resideInAPackage("de.fete.domain..")
.should().dependOnClassesThat().resideInAPackage("de.fete.adapter..");
@ArchTest
static final ArchRule domain_must_not_depend_on_application = noClasses()
.that().resideInAPackage("de.fete.domain..")
.should().dependOnClassesThat().resideInAPackage("de.fete.application..");
@ArchTest
static final ArchRule domain_must_not_depend_on_config = noClasses()
.that().resideInAPackage("de.fete.domain..")
.should().dependOnClassesThat().resideInAPackage("de.fete.config..");
@ArchTest
static final ArchRule inbound_ports_must_be_interfaces = classes()
.that().resideInAPackage("de.fete.domain.port.in..")
.should().beInterfaces();
@ArchTest
static final ArchRule outbound_ports_must_be_interfaces = classes()
.that().resideInAPackage("de.fete.domain.port.out..")
.should().beInterfaces();
@ArchTest
static final ArchRule domain_must_not_use_spring = noClasses()
.that().resideInAPackage("de.fete.domain..")
.should().dependOnClassesThat().resideInAPackage("org.springframework..");
@ArchTest
static final ArchRule web_must_not_depend_on_persistence = noClasses()
.that().resideInAPackage("de.fete.adapter.in.web..")
.should().dependOnClassesThat().resideInAPackage("de.fete.adapter.out.persistence..");
@ArchTest
static final ArchRule persistence_must_not_depend_on_web = noClasses()
.that().resideInAPackage("de.fete.adapter.out.persistence..")
.should().dependOnClassesThat().resideInAPackage("de.fete.adapter.in.web..");
}
```
**Verify**: `cd backend && ./mvnw test` passes and output shows `HexagonalArchitectureTest` with 9 tests.
---
### T-BP-10: Update CLAUDE.md `[x]`
**File**: `CLAUDE.md` (modify)
Add two rows to the Build Commands table:
| What | Command |
|------|---------|
| Backend checkstyle | `cd backend && ./mvnw checkstyle:check` |
| Backend full verify | `cd backend && ./mvnw verify` |
---
### T-BP-11: Final verification `[x]`
Run all verification commands to confirm the complete backpressure stack works:
1. `test -x .claude/hooks/backend-compile-check.sh`
2. `test -x .claude/hooks/frontend-type-check.sh`
3. `test -x .claude/hooks/run-tests.sh`
4. `python3 -c "import json; json.load(open('.claude/settings.json'))"`
5. `cd backend && ./mvnw verify` (triggers: Checkstyle → compile → test w/ ArchUnit → SpotBugs)
6. `cd frontend && npm run test:unit -- --run`
7. `echo '{"tool_input":{"file_path":"backend/src/main/java/de/fete/FeteApplication.java"}}' | CLAUDE_PROJECT_DIR=. .claude/hooks/backend-compile-check.sh`
8. `echo '{"tool_input":{"file_path":"frontend/src/App.vue"}}' | CLAUDE_PROJECT_DIR=. .claude/hooks/frontend-type-check.sh`
9. `echo '{"tool_input":{"file_path":"README.md"}}' | CLAUDE_PROJECT_DIR=. .claude/hooks/backend-compile-check.sh` (should be silent)
All commands must exit 0. If any fail, go back and fix the issue before marking complete.
---
## Addendum: Implementation Deviations (2026-03-04)
Changes made during implementation that deviate from or extend the original plan:
1. **Stop hook JSON schema**: The plan used `hookSpecificOutput` with `hookEventName: "Stop"` for the stop hook output. This is invalid — `hookSpecificOutput` is only supported for `PreToolUse`, `PostToolUse`, and `UserPromptSubmit` events. Fixed to use top-level `"decision": "approve"/"block"` with `"reason"` field.
2. **Stop hook loop prevention**: Added `stop_hook_active` check from stdin JSON to prevent infinite re-engagement loops. Not in original plan.
3. **Context-efficient test output**: Added `logback-test.xml` (root level WARN), `redirectTestOutputToFile=true`, and `trimStackTrace=true` to Surefire config. The stop hook script filters output to `[ERROR]` lines only, stripping Maven boilerplate. Not in original plan — added after observing that raw test failure output consumed excessive context.
4. **Hook path matching**: Case patterns in hook scripts extended to match both absolute and relative file paths (`*/backend/src/*.java|backend/src/*.java`). Original plan only had `*/backend/src/*.java` which doesn't match relative paths.
5. **Checkstyle `includeTestSourceDirectory`**: Set to `true` so test sources also follow Google Style. Not in original plan. `FeteApplicationTest.java` was reformatted to 2-space indentation with correct import order (static imports first).
6. **ArchUnit field naming**: Changed from `snake_case` (`onion_architecture_is_respected`) to `camelCase` (`onionArchitectureIsRespected`) to comply with Google Checkstyle rules.
---
## References
- Research document: `docs/agents/research/2026-03-04-backpressure-agentic-coding.md`
- Geoffrey Huntley: [Don't waste your back pressure](https://ghuntley.com/pressure/)
- JW: [If you don't engineer backpressure, you'll get slopped](https://jw.hn/engineering-backpressure)
- HumanLayer: [Context-Efficient Backpressure for Coding Agents](https://www.hlyr.dev/blog/context-efficient-backpressure)
- Claude Code: [Hooks Reference](https://code.claude.com/docs/en/hooks)
- ArchUnit: [User Guide](https://www.archunit.org/userguide/html/000_Index.html)

View File

@@ -0,0 +1,216 @@
---
date: 2026-03-04T01:40:21+01:00
git_commit: a55174b32333d0f46a55d94a50604344d1ba33f6
branch: master
topic: "Backpressure for Agentic Coding"
tags: [research, backpressure, agentic-coding, quality, tooling, hooks, static-analysis, archunit]
status: complete
---
# Research: Backpressure for Agentic Coding
## Research Question
What tools, methodologies, and patterns exist for implementing backpressure in agentic coding workflows? Which are applicable to the fete tech stack (Java 25, Spring Boot 3.5, Maven, Vue 3, TypeScript, Vitest)?
## Summary
Backpressure in agentic coding means: **automated feedback mechanisms that reject wrong output deterministically**, forcing the agent to self-correct before a human ever sees the result. The concept is borrowed from distributed systems (reactive streams, flow control) and applied to AI-assisted development.
The key insight from the literature: **90% deterministic, 10% agentic.** Encode constraints in the type system, linting rules, architecture tests, and test suites — not in prose instructions. The agent runs verification on its own output, sees failures, and fixes itself. Humans review only code that has already passed all automated gates.
### Core Sources
| Source | Author | Key Contribution |
|--------|--------|-----------------|
| [Don't waste your back pressure](https://ghuntley.com/pressure/) | Geoffrey Huntley | Coined "backpressure for agents." Feedback-driven quality, progressive delegation. |
| [If you don't engineer backpressure, you'll get slopped](https://jw.hn/engineering-backpressure) | JW | Verification hierarchy: types → linting → tests → agentic review. 90/10 rule. |
| [Context-Efficient Backpressure for Coding Agents](https://www.hlyr.dev/blog/context-efficient-backpressure) | HumanLayer | Output filtering, fail-fast, context window preservation. |
| [Claude Code Hooks Reference](https://code.claude.com/docs/en/hooks) | Anthropic | PostToolUse hooks for automated feedback after file edits. |
| [ArchUnit](https://www.archunit.org/) | TNG Technology Consulting | Architecture rules as unit tests. Hexagonal architecture enforcement. |
## Detailed Findings
### 1. The Backpressure Concept
In distributed systems, backpressure prevents upstream producers from overwhelming downstream consumers. Applied to agentic coding:
- **Producer:** The AI agent generating code
- **Consumer:** The quality gates (compiler, linter, tests, architecture rules)
- **Backpressure:** Automated rejection of output that doesn't pass gates
Geoffrey Huntley: *"If you aren't capturing your back-pressure then you are failing as a software engineer."*
The paradigm shift: instead of telling the agent what to do (prompt engineering), **engineer an environment where wrong outputs get rejected automatically** (backpressure engineering).
### 2. The Verification Hierarchy
JW's article establishes a strict ordering — deterministic first, agentic last:
```
Layer 1: Type System (hardest constraint, compile-time)
Layer 2: Static Analysis (linting rules, pattern enforcement)
Layer 3: Architecture Tests (dependency rules, layer violations)
Layer 4: Unit/Integration Tests (behavioral correctness)
Layer 5: Agentic Review (judgment calls — only after 1-4 pass)
```
**Critical rule:** If a constraint can be checked deterministically, it MUST be checked deterministically. Relying on agentic review for things a linter could catch is "building on sand."
**Context efficiency:** Don't dump rules into CLAUDE.md that could be expressed as type constraints, lint rules, or tests. Reserve documentation for architectural intent and domain knowledge that genuinely requires natural language.
### 3. Context-Efficient Output
HumanLayer's research on context window management for coding agents:
- **On success:** Show only `✓` — don't waste tokens on 200 lines of passing test output
- **On failure:** Show the full error — the agent needs the details to self-correct
- **Fail-fast:** Enable `--bail` / `-x` / `-failfast` — one failure at a time prevents context-switching between multiple bugs
- **Filter output:** Strip generic stack frames, timing info, and irrelevant details
**Anti-pattern:** Piping output to `/dev/null` or using `head -n 50` — this hides information the agent might need and can force repeated test runs.
### 4. Claude Code Hooks
Hooks are shell commands that execute automatically at specific points in Claude Code's lifecycle:
| Event | Trigger | Use Case |
|-------|---------|----------|
| `PreToolUse` | Before a tool runs | Block dangerous operations |
| `PostToolUse` | After a tool completes | Run compile/lint/test checks |
| `Stop` | Agent finishes response | Final validation |
| `UserPromptSubmit` | User sends a prompt | Inject context |
| `SessionStart` | Session begins | Setup checks |
**PostToolUse** is the primary backpressure mechanism: after every file edit, run deterministic checks and feed the result back to the agent.
**Configuration:** `.claude/settings.json` (project-level, committed) or `.claude/settings.local.json` (personal, gitignored).
**Hook format example:**
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit:*.java",
"hooks": [
{
"type": "command",
"command": "cd backend && ./mvnw compile -q 2>&1 || true"
}
]
}
]
}
}
```
The hook output is fed back to the agent as context, enabling self-correction in the same conversation turn.
### 5. Applicable Tools for fete's Tech Stack
#### 5.1 Java / Maven Backend
**Checkstyle** (coding conventions)
- Maven plugin: `maven-checkstyle-plugin`
- Enforces formatting, naming, imports, Javadoc rules
- Rulesets: Google Style (most widely adopted), Sun Style (legacy)
- Fails build on violation when configured with `<failOnViolation>true</failOnViolation>`
- Actively maintained, open source (LGPL-2.1)
**SpotBugs** (bug detection)
- Maven plugin: `spotbugs-maven-plugin`
- Successor to FindBugs — finds null pointer dereferences, infinite loops, resource leaks, concurrency bugs
- Runs bytecode analysis (requires compilation first)
- Configurable effort/threshold levels
- Actively maintained, open source (LGPL-2.1)
**Error Prone** (compile-time bug detection)
- Google's javac plugin — catches errors during compilation
- Tighter feedback loop than SpotBugs (compile-time vs. post-compile)
- Requires `maven-compiler-plugin` configuration with annotation processor
- More invasive setup, Java version compatibility can lag
- Actively maintained, open source (Apache-2.0)
**ArchUnit** (architecture enforcement)
- Library for writing architecture rules as JUnit tests
- Built-in support for onion/hexagonal architecture via `onionArchitecture()`
- Dedicated hexagonal ruleset: [archunit-hexagonal](https://github.com/whiskeysierra/archunit-hexagonal)
- Rules: "domain must not depend on adapters", "ports are interfaces", "no Spring annotations in domain"
- Fails as a normal test — agent sees the failure and can fix it
- Actively maintained, open source (Apache-2.0)
#### 5.2 Vue 3 / TypeScript Frontend
**TypeScript strict mode** (already configured)
- `strict: true` via `@vue/tsconfig`
- `noUncheckedIndexedAccess: true` (already in `tsconfig.app.json`)
- `vue-tsc --build` for type-checking (already in `package.json` as `type-check`)
**ESLint + oxlint** (already configured)
- ESLint with `@vue/eslint-config-typescript` (recommended rules)
- oxlint as fast pre-pass (Rust-based, handles simple rules)
- Custom ESLint rules can encode repeated agent mistakes
**Vitest** (already configured)
- `--bail` flag available for fail-fast behavior
- `--reporter=verbose` for detailed output on failure
### 6. Current State Analysis (fete project)
| Layer | Backend | Frontend |
|-------|---------|----------|
| Type System | Java 25 (strong, but no extra strictness configured) | TypeScript strict + `noUncheckedIndexedAccess` ✓ |
| Static Analysis | **Nothing configured** | ESLint + oxlint + Prettier ✓ |
| Architecture Tests | **Nothing configured** | N/A (flat structure) |
| Unit Tests | JUnit 5 via `./mvnw test` ✓ | Vitest via `npm run test:unit` ✓ |
| Claude Code Hooks | **Not configured** | **Not configured** |
| Fail-fast | **Not configured** | **Not configured** |
**Gaps:** The backend has zero static analysis or architecture enforcement. Claude Code hooks don't exist yet. Neither side has fail-fast configured.
### 7. Evaluation: What to Implement
| Measure | Effort | Impact | Privacy OK | Maintained | Recommendation |
|---------|--------|--------|------------|------------|----------------|
| Claude Code Hooks (PostToolUse) | Low | High | Yes (local) | N/A (config) | **Immediate** |
| Fail-fast + output filtering | Low | Medium | Yes (local) | N/A (config) | **Immediate** |
| Checkstyle Maven plugin | Low | Medium | Yes (no network) | Yes (LGPL) | **Yes** |
| SpotBugs Maven plugin | Low | Medium | Yes (no network) | Yes (LGPL) | **Yes** |
| ArchUnit hexagonal tests | Medium | High | Yes (no network) | Yes (Apache) | **Yes** |
| Error Prone | Medium | Medium | Yes (no network) | Yes (Apache) | **Defer** — overlaps with SpotBugs, more invasive setup, Java 25 compatibility uncertain |
| Custom ESLint rules | Low | Low-Medium | Yes (local) | N/A (project rules) | **As needed** — add rules when recurring agent mistakes are observed |
| MCP LSP Server | High | Medium | Yes (local) | Varies | **Defer** — experimental, high setup cost, unclear benefit vs. hooks |
### 8. Tool Compatibility Notes
**Java 25 compatibility:**
- Checkstyle: Confirmed support for Java 21+, Java 25 should work (runs on source, not bytecode)
- SpotBugs: Bytecode analysis — needs ASM version that supports Java 25 classfiles. Latest SpotBugs (4.9.x) supports up to Java 24; Java 25 support may require a newer release. **Verify before adopting.**
- ArchUnit: Runs via JUnit, analyzes compiled classes. Similar ASM dependency concern as SpotBugs. **Verify before adopting.**
- Error Prone: Tightly coupled to javac internals. Java 25 compatibility typically lags. **Higher risk.**
**Privacy compliance:** All recommended tools are offline-only. None phone home, none require external services. All are open source with permissive or copyleft licenses compatible with GPL.
## Decisions Required
| # | Decision | Options | Recommendation |
|---|----------|---------|----------------|
| 1 | Hooks in which settings file? | `.claude/settings.json` (project, committed) vs. `.claude/settings.local.json` (personal, gitignored) | **Project-level** — every agent user benefits |
| 2 | Checkstyle ruleset | Google Style vs. Sun Style vs. custom | **Google Style** — most widely adopted, well-documented |
| 3 | Include Error Prone in plan? | Yes (more coverage) vs. defer (simpler, overlap with SpotBugs) | **Defer** — Java 25 compatibility uncertain, overlaps with SpotBugs |
## References
- Geoffrey Huntley: [Don't waste your back pressure](https://ghuntley.com/pressure/)
- JW: [If you don't engineer backpressure, you'll get slopped](https://jw.hn/engineering-backpressure)
- HumanLayer: [Context-Efficient Backpressure for Coding Agents](https://www.hlyr.dev/blog/context-efficient-backpressure)
- Anthropic: [Claude Code Hooks Reference](https://code.claude.com/docs/en/hooks)
- Anthropic: [2026 Agentic Coding Trends Report](https://resources.anthropic.com/hubfs/2026%20Agentic%20Coding%20Trends%20Report.pdf)
- ArchUnit: [User Guide](https://www.archunit.org/userguide/html/000_Index.html)
- ArchUnit Hexagonal: [GitHub](https://github.com/whiskeysierra/archunit-hexagonal)
- SpotBugs: [Documentation](https://spotbugs.github.io/)
- Checkstyle: [Documentation](https://checkstyle.sourceforge.io/)
- Claude Code Hooks Guide: [Luiz Tanure](https://www.letanure.dev/blog/2025-08-06--claude-code-part-8-hooks-automated-quality-checks)
- lsp-mcp: [GitHub](https://github.com/jonrad/lsp-mcp)

View File

@@ -9,6 +9,7 @@ export default mergeConfig(
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'e2e/**'],
root: fileURLToPath(new URL('./', import.meta.url)),
bail: 1,
},
}),
)