Skip to content

Commit bea8d76

Browse files
committed
Add the AI agents page
The page backs the tagline: why the formatter is the first check between an edit and a merge, what it cannot tell you, and four places to run it for an agent: a Claude Code hook, a section for AGENTS.md, the pre-commit hook and CI. The hook script was run against the published 2.98.0.1 native binary with the PostToolUse payload the Claude Code documentation describes, and its settings follow the form that documentation recommends; it was exercised with simulated payloads, not inside a live session. Two findings from that run shape the recipes. A full format deletes an import the agent has added but not used yet, so the hook passes --skip-removing-unused-imports and leaves the real leftovers to the pre-commit hook and CI, which both run the full check. And formatDiff reads git diff HEAD, which leaves out the new files an agent creates, so the Gradle variant of the AGENTS.md section runs git add -N first. The home page now links its mention of an agent hook to the page.
1 parent 8ca149e commit bea8d76

3 files changed

Lines changed: 197 additions & 3 deletions

File tree

‎docs/ai-agents.md‎

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# AI agents
2+
3+
A coding agent does not know your team's style, and with open-java-format it does not have to.
4+
Format every file the agent writes, and the code arrives in the one style, whichever model wrote
5+
it. This page explains why that check comes first, what it cannot tell you, and how to set it up at
6+
four points: after each edit, in the agent's instructions, before a commit and in CI.
7+
8+
## Why formatting comes first
9+
10+
Every check between an edit and a merge answers its own question, and each one needs more than the
11+
one before it.
12+
13+
| Check | What it tells you | What it needs |
14+
| --- | --- | --- |
15+
| Formatter | The file parses, and it is laid out in the one style | The file |
16+
| Compiler | The code type-checks | The module and its dependencies |
17+
| Static analysis | No known bug pattern matched | Usually a compiled module |
18+
| Tests | The behaviour under test still holds | A build that runs |
19+
| Review | The change is the right one | A person's time |
20+
21+
The formatter is first in that chain for four reasons.
22+
23+
- **It needs one file.** There is no build, no classpath and no project model. Halfway through a
24+
refactoring the project does not compile, but every file the agent has touched can still be
25+
formatted.
26+
- **It is fast.** The native binary has no JVM to start. On an Apple silicon laptop the whole
27+
[hook](#after-every-edit-a-claude-code-hook) below takes about 50 ms for a 270-line file and
28+
about 0.6 s for a 4,000-line one, so it can run after every single edit.
29+
- **It parses the file.** A file with a syntax error fails at once, with the file, line and column.
30+
The agent hears about a missing semicolon before it spends a build on it.
31+
- **There is nothing to configure.** There is no style to describe in a prompt and no option for a
32+
model to get wrong. The output depends only on the input.
33+
34+
## What the formatter does not tell you
35+
36+
A formatter does not find bugs. open-java-format checks that a file parses and lays it out. It does
37+
not resolve a single type or symbol.
38+
39+
``` java
40+
public class Typo {
41+
int f() {
42+
return "text";
43+
}
44+
45+
void g() {
46+
undefinedMethod();
47+
}
48+
}
49+
```
50+
51+
Neither method compiles, and the formatter exits with 0. The compiler, static analysis, tests and
52+
review still have all of their work to do. Formatting first only means that they get code in one
53+
shape, and that a reviewer's diff shows what changed in the logic.
54+
55+
## Set it up
56+
57+
The four layers back each other up, so use as many as you can.
58+
59+
| Layer | Runs | Misses |
60+
| --- | --- | --- |
61+
| [Claude Code hook](#after-every-edit-a-claude-code-hook) | After every file the agent edits | Files the agent changes through a shell command |
62+
| [AGENTS.md](#in-the-agents-instructions-agentsmd) | When the agent follows its instructions | Whatever the model forgets: an instruction is context, not enforcement |
63+
| [pre-commit hook](#before-a-commit-the-pre-commit-hook) | Before every commit | Machines where nobody installed it, and commits made with `--no-verify` |
64+
| [CI](#in-ci-the-last-gate) | On every pull request and push | Nothing that reaches a pull request |
65+
66+
### After every edit: a Claude Code hook
67+
68+
[Claude Code hooks](https://code.claude.com/docs/en/hooks-guide) run a command at fixed points of a
69+
session. A `PostToolUse` hook on the `Edit` and `Write` tools runs after every file the agent
70+
changes, and it gets the tool call as JSON on its standard input.
71+
72+
The hook needs `open-java-format` on the `PATH`, see [Command line](get-started/command-line.md),
73+
and [`jq`](https://jqlang.org/).
74+
75+
``` sh title=".claude/hooks/format-java.sh"
76+
#!/bin/sh
77+
# Claude Code runs this after every Edit and Write and passes the tool call as JSON on stdin.
78+
file=$(jq -r '.tool_input.file_path // empty')
79+
80+
case "$file" in
81+
*.java) ;;
82+
*) exit 0 ;;
83+
esac
84+
85+
# Exit code 2 makes Claude Code show the formatter's message to the model.
86+
open-java-format --ojf --skip-removing-unused-imports --replace "$file" || exit 2
87+
```
88+
89+
``` sh
90+
chmod +x .claude/hooks/format-java.sh
91+
```
92+
93+
``` json title=".claude/settings.json"
94+
{
95+
"hooks": {
96+
"PostToolUse": [
97+
{
98+
"matcher": "Edit|Write",
99+
"hooks": [
100+
{
101+
"type": "command",
102+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format-java.sh",
103+
"args": []
104+
}
105+
]
106+
}
107+
]
108+
}
109+
}
110+
```
111+
112+
Commit both files, and everyone who opens the project in Claude Code gets the hook. To keep it to
113+
yourself, put the `hooks` block into `.claude/settings.local.json` instead.
114+
115+
**Unused imports stay for now.** An agent often adds an import in one edit and the code that uses
116+
it in the next. A full format after the first edit would delete that import, so the hook passes
117+
`--skip-removing-unused-imports`. The pre-commit hook and CI run the full check, and they catch the
118+
imports that really are unused.
119+
120+
**A file that does not parse goes back to the agent.** The formatter leaves the file as it is and
121+
prints the error. The script then exits with 2, the exit code that makes Claude Code show a hook's
122+
message to the model, so Claude sees it right after its edit:
123+
124+
``` text
125+
src/main/java/com/example/Broken.java:5:22: error: ';' expected
126+
```
127+
128+
The hook does not see a file that the agent rewrites through a shell command such as `sed -i`. The
129+
later layers cover those.
130+
131+
### In the agent's instructions: AGENTS.md
132+
133+
[AGENTS.md](https://agents.md/) is a Markdown file in the repository root that holds instructions
134+
for coding agents. Add a section that matches how the project runs the formatter.
135+
136+
=== "Command line"
137+
138+
```` markdown title="AGENTS.md"
139+
## Java formatting
140+
141+
Java code in this repository is formatted with open-java-format. It has one style and no
142+
options, so never lay out code by hand and never try to match the lines around your change.
143+
144+
After you create or edit a `.java` file, format it:
145+
146+
```sh
147+
open-java-format --ojf --replace path/to/File.java
148+
```
149+
150+
Before you commit, run this check. It must print nothing, so format every file it lists:
151+
152+
```sh
153+
open-java-format --ojf --dry-run --set-exit-if-changed $(git ls-files '*.java')
154+
```
155+
````
156+
157+
=== "Gradle plugin"
158+
159+
```` markdown title="AGENTS.md"
160+
## Java formatting
161+
162+
Java code in this repository is formatted with open-java-format. It has one style and no
163+
options, so never lay out code by hand and never try to match the lines around your change.
164+
165+
After you change Java code, and again before you commit, run:
166+
167+
```sh
168+
git add -N . && ./gradlew formatDiff
169+
```
170+
````
171+
172+
`formatDiff` reads `git diff HEAD`, which leaves out files that git does not track yet.
173+
`git add -N .` marks the files the agent created, so that they are formatted too.
174+
175+
An instruction is context, not enforcement. A model can forget it in a long session, which is what
176+
the hook above and the two checks below are for.
177+
178+
Claude Code [reads `AGENTS.md`](https://code.claude.com/docs/en/memory#agents-md) from version
179+
2.1.277 on, as long as the project has no `CLAUDE.md`. If it has one, import the file there with a
180+
line that says `@AGENTS.md`.
181+
182+
### Before a commit: the pre-commit hook
183+
184+
An agent that commits runs the repository's git hooks like anyone else. The
185+
[pre-commit hook](get-started/github-actions.md#git-pre-commit-hook) checks the staged `.java`
186+
files, stops the commit when one is not formatted and prints the command that fixes it, which is
187+
all an agent needs to recover.
188+
189+
### In CI: the last gate
190+
191+
An agent that works in the cloud and opens a pull request runs none of your local hooks. The
192+
[GitHub Action](get-started/github-actions.md#check-pull-requests-and-pushes) checks every pull
193+
request, whoever or whatever wrote it, and it needs no Java on the runner.

‎docs/index.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ so every file comes out the same way, whether a person or a model wrote it.
3636

3737
---
3838

39-
The formatter ships as a native binary with no JVM to start, so it fits into an agent hook, a
40-
pre-commit hook or a CI step. It also comes as a Gradle plugin and as plugins for IntelliJ IDEA
41-
and Eclipse.
39+
The formatter ships as a native binary with no JVM to start, so it fits into an
40+
[agent hook](ai-agents.md), a pre-commit hook or a CI step. It also comes as a Gradle plugin
41+
and as plugins for IntelliJ IDEA and Eclipse.
4242

4343
</div>
4444

‎zensical.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ nav = [
2424
{ "Eclipse" = "get-started/eclipse.md" },
2525
{ "GitHub Action and pre-commit" = "get-started/github-actions.md" },
2626
] },
27+
{ "AI agents" = "ai-agents.md" },
2728
{ "Manifesto" = "manifesto.md" },
2829
]
2930

0 commit comments

Comments
 (0)