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:
487
docs/agents/plan/2026-03-04-backpressure-agentic-coding.md
Normal file
487
docs/agents/plan/2026-03-04-backpressure-agentic-coding.md
Normal 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)
|
||||
216
docs/agents/research/2026-03-04-backpressure-agentic-coding.md
Normal file
216
docs/agents/research/2026-03-04-backpressure-agentic-coding.md
Normal 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)
|
||||
Reference in New Issue
Block a user