Skip to content

Commit 2dfafcd

Browse files
committed
Add README with build/install/usage instructions, fix build config
- Add comprehensive README: building, installing (zip/runIde/custom IDE), using the plugin, agent integration examples, file format spec, project structure - Fix build.gradle.kts: add jvmToolchain(17), disable buildSearchableOptions, add description to pluginConfiguration - Fix plugin.xml: remove redundant serviceInterface (same as implementation) https://claude.ai/code/session_015LpmjAt17XD582hZ9TPLCv
1 parent a5b4e78 commit 2dfafcd

3 files changed

Lines changed: 286 additions & 9 deletions

File tree

review-plugin/README.md

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
# Agent Review — IntelliJ Plugin
2+
3+
A Kotlin/IntelliJ plugin that renders file-based code review annotations written by a human
4+
or a coding agent. No server. No network. The protocol is a directory of JSON files. The
5+
plugin reads and writes them. The agent reads and writes them. Both parties see each other's
6+
comments in real time.
7+
8+
## Requirements
9+
10+
- **JDK 17+** (required for IntelliJ Platform 2024.1)
11+
- **IntelliJ IDEA 2024.1+** (Community or Ultimate), or any JetBrains IDE based on the
12+
same platform (CLion, WebStorm, PyCharm, etc.)
13+
- **Git** on PATH (for anchor resolution via `git diff`)
14+
15+
## Building
16+
17+
```bash
18+
cd review-plugin
19+
20+
# Build the plugin distribution zip
21+
./gradlew buildPlugin
22+
23+
# The installable zip is at:
24+
# build/distributions/review-plugin-0.1.0.zip
25+
```
26+
27+
To also run the tests:
28+
29+
```bash
30+
./gradlew test
31+
```
32+
33+
To verify plugin descriptor compatibility:
34+
35+
```bash
36+
./gradlew verifyPluginConfiguration
37+
```
38+
39+
## Installing in IntelliJ
40+
41+
### Option A: Install from disk (built zip)
42+
43+
1. Build the plugin: `./gradlew buildPlugin`
44+
2. Open IntelliJ IDEA
45+
3. Go to **Settings****Plugins** → gear icon (⚙) → **Install Plugin from Disk...**
46+
4. Select `review-plugin/build/distributions/review-plugin-0.1.0.zip`
47+
5. Restart the IDE
48+
49+
### Option B: Run a sandboxed IDE instance (for development)
50+
51+
This launches a fresh IntelliJ instance with the plugin pre-installed — your main IDE
52+
settings are not affected:
53+
54+
```bash
55+
cd review-plugin
56+
./gradlew runIde
57+
```
58+
59+
This is the best way to test during development. It downloads IntelliJ Community 2024.1
60+
automatically on first run.
61+
62+
### Option C: Run in a specific IDE
63+
64+
```bash
65+
# Run in a specific IntelliJ installation
66+
./gradlew runIde -PalternativeIdePath="/path/to/IntelliJIDEA"
67+
```
68+
69+
## Using the Plugin
70+
71+
### Creating a comment (human → agent)
72+
73+
1. Open any file in the editor
74+
2. Select the lines you want to comment on (or place your cursor on a single line)
75+
3. Right-click → **Add Review Comment** (or press **Ctrl+Alt+R**)
76+
4. Type your comment and click OK
77+
78+
This writes a JSON file to `.review/comments/<id>.json` in the project root.
79+
80+
### Viewing comments
81+
82+
- **Gutter icons**: Red dot = open, green check = resolved, grey dash = won't fix.
83+
Hover for a tooltip with the comment body.
84+
- **Tool window**: Click **Code Review** in the bottom tool window bar. Use the
85+
filter buttons (All / Open / Resolved) to narrow the list.
86+
- **Navigation**: Click a gutter icon or a comment in the tool window to jump to
87+
the annotated code.
88+
89+
### Replying / resolving
90+
91+
In the Code Review tool window, select a comment, then:
92+
- **Reply**: Type in the reply box and click "Reply"
93+
- **Resolve**: Click "Resolve" (optionally add a reply message)
94+
- **Won't Fix**: Click "Won't Fix"
95+
96+
### Ghost hunks
97+
98+
When a comment is resolved and the code has changed, the plugin renders the
99+
original code as a greyed-out block inlay above the current position, so you can
100+
see what was there before.
101+
102+
## Agent Integration
103+
104+
An agent (Claude Code, Cursor, or any script) participates by reading and writing
105+
JSON files in `.review/comments/`. No special library required.
106+
107+
### Reading open comments (Python example)
108+
109+
```python
110+
import json, glob, os
111+
112+
def get_open_comments(project_root):
113+
pattern = os.path.join(project_root, ".review", "comments", "*.json")
114+
comments = []
115+
for path in glob.glob(pattern):
116+
with open(path) as f:
117+
c = json.load(f)
118+
if c["status"] == "open":
119+
comments.append(c)
120+
return comments
121+
```
122+
123+
### Writing a new comment
124+
125+
```python
126+
import json, os, random, subprocess
127+
from datetime import datetime, timezone
128+
129+
def post_comment(project_root, file_rel, line, target_lines,
130+
context_before, context_after, body):
131+
cid = random.randbytes(4).hex()
132+
commit = subprocess.check_output(
133+
["git", "rev-parse", "HEAD"],
134+
cwd=project_root).decode().strip()
135+
136+
comment = {
137+
"id": cid,
138+
"schema_version": 1,
139+
"author": "agent",
140+
"created": datetime.now(timezone.utc).isoformat(),
141+
"status": "open",
142+
"anchor": {
143+
"file": file_rel,
144+
"commit": commit,
145+
"line_hint": line,
146+
"hunk": {
147+
"context_before": context_before,
148+
"target": target_lines,
149+
"context_after": context_after
150+
}
151+
},
152+
"resolved_anchor": None,
153+
"body": body,
154+
"thread": []
155+
}
156+
157+
dir_path = os.path.join(project_root, ".review", "comments")
158+
os.makedirs(dir_path, exist_ok=True)
159+
tmp = os.path.join(dir_path, f"{cid}.json.tmp")
160+
final = os.path.join(dir_path, f"{cid}.json")
161+
with open(tmp, "w") as f:
162+
json.dump(comment, f, indent=2)
163+
os.rename(tmp, final) # atomic write
164+
```
165+
166+
### Resolving a comment
167+
168+
```python
169+
def resolve_comment(project_root, comment_id, reply_body):
170+
path = os.path.join(project_root, ".review", "comments", f"{comment_id}.json")
171+
with open(path) as f:
172+
comment = json.load(f)
173+
174+
comment["status"] = "resolved"
175+
comment["thread"].append({
176+
"id": random.randbytes(4).hex(),
177+
"author": "agent",
178+
"created": datetime.now(timezone.utc).isoformat(),
179+
"body": reply_body,
180+
"status": "resolved"
181+
})
182+
183+
tmp = path + ".tmp"
184+
with open(tmp, "w") as f:
185+
json.dump(comment, f, indent=2)
186+
os.rename(tmp, path)
187+
```
188+
189+
## File Format
190+
191+
Each comment is a single JSON file in `.review/comments/`:
192+
193+
```
194+
<project-root>/
195+
└── .review/
196+
└── comments/
197+
├── a3f1c2d4.json
198+
└── b7e902f1.json
199+
```
200+
201+
| Field | Type | Description |
202+
|---|---|---|
203+
| `id` | string | 8-char hex, unique per file |
204+
| `schema_version` | int | Always `1`. Plugin skips unknown versions |
205+
| `author` | string | Free string — conventionally `"human"` or `"agent"` |
206+
| `status` | enum | `"open"` / `"resolved"` / `"wontfix"` |
207+
| `anchor.file` | string | Project-relative path, forward slashes |
208+
| `anchor.commit` | string | Git SHA at comment creation time |
209+
| `anchor.line_hint` | int | 1-based line number (hint, not authoritative) |
210+
| `anchor.hunk` | object | `context_before`, `target`, `context_after` (string arrays) |
211+
| `resolved_anchor` | object? | Same shape as `anchor`, written when code changes during resolution |
212+
| `body` | string | The comment text |
213+
| `thread` | array | Append-only reply list |
214+
215+
### Rules for writers
216+
217+
- Never modify `id`, `created`, `anchor`, or `author` after initial write
218+
- To resolve: set root `status` to `"resolved"` and append a thread entry
219+
- To reply: append to `thread[]` — never rewrite existing entries
220+
- Write atomically: write to `<id>.json.tmp` then rename to `<id>.json`
221+
- One comment per file, filename is always `<id>.json`
222+
223+
## Anchor Resolution
224+
225+
When the file has changed since the comment was created, the plugin resolves
226+
the comment position through a 3-step algorithm:
227+
228+
1. **Exact match** — Check if lines at `line_hint` still match `hunk.target` (trimmed)
229+
2. **Git diff remap** — Parse `git diff <commit>` to compute line offset
230+
3. **Fuzzy search** — Sliding-window LCS over the full file (threshold: 0.75)
231+
4. **Drifted** — If all steps fail, the comment is shown only in the tool window
232+
233+
## Project Structure
234+
235+
```
236+
review-plugin/
237+
├── build.gradle.kts
238+
├── settings.gradle.kts
239+
├── gradle.properties
240+
└── src/
241+
├── main/kotlin/com/reviewplugin/
242+
│ ├── model/ # ReviewComment, Anchor, Hunk, ThreadEntry
243+
│ ├── store/ # CommentStore (project service), ReviewDirWatcher
244+
│ ├── anchor/ # HunkMatcher (pure algorithms), AnchorResolver, GitRunner
245+
│ ├── ui/
246+
│ │ ├── gutter/ # ReviewGutterIconProvider (line markers)
247+
│ │ ├── inlay/ # GhostHunkRenderer, GhostHunkInlayManager
248+
│ │ └── toolwindow/ # ReviewToolWindowFactory, ReviewToolWindowPanel
249+
│ └── actions/ # NewReviewCommentAction
250+
├── main/resources/
251+
│ ├── META-INF/plugin.xml
252+
│ └── icons/review.svg
253+
└── test/kotlin/com/reviewplugin/
254+
├── model/ # ReviewCommentTest (18 tests)
255+
├── anchor/ # HunkMatcherTest (22 tests)
256+
└── store/ # CommentStoreFileIOTest (13 tests)
257+
```
258+
259+
## CI
260+
261+
GitHub Actions runs on every push to `main` and `claude/**` branches:
262+
263+
- **build-and-test**: Compiles the plugin, runs all 53 unit tests
264+
- **verify-plugin**: Validates the plugin descriptor, produces the installable zip as an artifact
265+
266+
## Sharing Review State
267+
268+
The `.review/` directory is designed to be committed to git. This way:
269+
- Comments persist across IDE restarts
270+
- Agent and human share annotation state across sessions
271+
- Review history is preserved in version control
272+
273+
Add `.review/` to `.gitignore` only if you want annotations to be local-only.

review-plugin/build.gradle.kts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ repositories {
1414
}
1515
}
1616

17+
kotlin {
18+
jvmToolchain(17)
19+
}
20+
1721
dependencies {
1822
intellijPlatform {
1923
intellijIdeaCommunity("2024.1")
@@ -31,6 +35,12 @@ intellijPlatform {
3135
id = "com.reviewplugin"
3236
name = "Agent Review"
3337
version = project.version.toString()
38+
description = """
39+
Renders file-based code review annotations written by a human or a coding agent.
40+
No server. No network. The protocol is a directory of JSON files under .review/comments/.
41+
The plugin reads and writes them. The agent reads and writes them.
42+
Both parties see each other's comments in real time.
43+
""".trimIndent()
3444
ideaVersion {
3545
sinceBuild = "241"
3646
}
@@ -41,4 +51,7 @@ tasks {
4151
test {
4252
useJUnit()
4353
}
54+
buildSearchableOptions {
55+
enabled = false
56+
}
4457
}

review-plugin/src/main/resources/META-INF/plugin.xml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,16 @@
22
<id>com.reviewplugin</id>
33
<name>Agent Review</name>
44
<vendor>btraceio</vendor>
5-
<description>
6-
Renders file-based code review annotations written by a human or a coding agent.
7-
No server. No network. The protocol is a directory of JSON files.
8-
</description>
95

106
<depends>com.intellij.modules.platform</depends>
117

128
<extensions defaultExtensionNs="com.intellij">
13-
<!-- Store as project service -->
149
<projectService
15-
serviceInterface="com.reviewplugin.store.CommentStore"
1610
serviceImplementation="com.reviewplugin.store.CommentStore"/>
1711

18-
<!-- Startup: register file watcher and load existing comments -->
1912
<postStartupActivity
2013
implementation="com.reviewplugin.store.ReviewDirWatcher"/>
2114

22-
<!-- Gutter markers for common languages -->
2315
<codeInsight.lineMarkerProvider
2416
language="JAVA"
2517
implementationClass="com.reviewplugin.ui.gutter.ReviewGutterIconProvider"/>
@@ -45,7 +37,6 @@
4537
language="TEXT"
4638
implementationClass="com.reviewplugin.ui.gutter.ReviewGutterIconProvider"/>
4739

48-
<!-- Tool window -->
4940
<toolWindow id="Code Review"
5041
anchor="bottom"
5142
factoryClass="com.reviewplugin.ui.toolwindow.ReviewToolWindowFactory"

0 commit comments

Comments
 (0)