pi-review-loop 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +332 -0
- package/index.ts +280 -0
- package/package.json +34 -0
- package/prompts/double-check-plan.md +10 -0
- package/prompts/double-check.md +10 -0
- package/review-loop.png +0 -0
- package/scripts/install.js +51 -0
- package/scripts/uninstall.js +19 -0
- package/settings.ts +211 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nico Bailon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
<p>
|
|
2
|
+
<img src="review-loop.png" alt="pi-review-loop" width="1100">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# Pi Review Loop
|
|
6
|
+
|
|
7
|
+
Automated code review loop for pi. Repeatedly prompts the agent to review its own work until it confirms no issues remain.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
> implement the plan
|
|
11
|
+
|
|
12
|
+
[agent implements...]
|
|
13
|
+
|
|
14
|
+
Review mode (1/7) ← status appears in footer
|
|
15
|
+
|
|
16
|
+
[agent reviews, finds bug, fixes it]
|
|
17
|
+
|
|
18
|
+
Review mode (2/7)
|
|
19
|
+
|
|
20
|
+
[agent reviews again]
|
|
21
|
+
|
|
22
|
+
"No issues found."
|
|
23
|
+
|
|
24
|
+
Review mode ended: no issues found ← auto-exits
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Why
|
|
28
|
+
|
|
29
|
+
Agents make mistakes. They miss edge cases, introduce typos, forget error handling. Asking them to review their own code catches a surprising number of issues, but you have to remember to ask, and then ask again if they found something.
|
|
30
|
+
|
|
31
|
+
Reviewer Loop automates this:
|
|
32
|
+
|
|
33
|
+
**Auto-Trigger** - Detects phrases like "implement the plan" or the `/double-check` template. No need to manually activate.
|
|
34
|
+
|
|
35
|
+
**Persistent Loop** - After each response, sends a review prompt. If the agent found and fixed issues, it loops again. Only exits when the agent genuinely finds nothing.
|
|
36
|
+
|
|
37
|
+
**Smart Exit Detection** - Won't be fooled by "Fixed 3 issues. No further issues found." Detects when issues were fixed and keeps looping.
|
|
38
|
+
|
|
39
|
+
**Fully Configurable** - Every pattern is customizable. Change what triggers the loop, what exits it, and what prompt gets sent. Extend the defaults or replace them entirely.
|
|
40
|
+
|
|
41
|
+
## Typical Workflow
|
|
42
|
+
|
|
43
|
+
The loop shines in two scenarios:
|
|
44
|
+
|
|
45
|
+
**Before implementing** — You've got a plan doc and want to sanity-check it against the actual codebase. Run `/double-check-plan` and let the agent compare the plan to what exists. It'll catch things like outdated assumptions, conflicting patterns, or unnecessary complexity. The funny thing is, it rarely finds everything on the first pass. Second pass catches different issues. Third pass, more still. That's the whole point of the loop.
|
|
46
|
+
|
|
47
|
+
**After implementing** — You just finished building a feature and want to catch bugs before calling it done. Run `/double-check` and the agent reviews its own work with fresh eyes. Typos, missed edge cases, forgotten error handling — it finds stuff you'd miss staring at the same code. Again, multiple passes tend to surface different issues each time.
|
|
48
|
+
|
|
49
|
+
The pattern is the same: keep reviewing until there's genuinely nothing left to find. The loop handles the "ask again" part automatically. You'll see `Review mode (2/7)` in the footer so you know it's working and how many passes it's done.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
**npm** (recommended):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install -g pi-review-loop
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The extension is automatically copied to `~/.pi/agent/extensions/pi-review-loop/`.
|
|
60
|
+
|
|
61
|
+
**Or clone directly:**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/nicobailon/pi-review-loop ~/.pi/agent/extensions/pi-review-loop
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Restart pi to load the extension. On activation, you'll see status in the footer:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Review mode (2/7)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Prompt Templates
|
|
74
|
+
|
|
75
|
+
The package includes two prompt templates that are automatically installed to `~/.pi/agent/prompts/` (if they don't already exist):
|
|
76
|
+
|
|
77
|
+
| Template | Command | Description |
|
|
78
|
+
|----------|---------|-------------|
|
|
79
|
+
| `double-check.md` | `/double-check` | Review code with fresh eyes, fix any issues found |
|
|
80
|
+
| `double-check-plan.md` | `/double-check-plan` | Review implementation plan against codebase |
|
|
81
|
+
|
|
82
|
+
These prompts are designed to work with the review loop:
|
|
83
|
+
- They include the "fresh eyes" phrase that auto-triggers the loop
|
|
84
|
+
- They instruct the agent to respond with "No issues found." when done (triggering exit)
|
|
85
|
+
- They tell the agent to end with "Fixed [N] issue(s). Ready for another review." when issues are fixed (continuing the loop)
|
|
86
|
+
|
|
87
|
+
**Manual installation** (if cloning instead of npm):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cp ~/.pi/agent/extensions/pi-review-loop/prompts/*.md ~/.pi/agent/prompts/
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Quick Start
|
|
94
|
+
|
|
95
|
+
### Automatic Activation
|
|
96
|
+
|
|
97
|
+
Just use trigger phrases naturally:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
> implement the plan
|
|
101
|
+
> implement the spec
|
|
102
|
+
> let's implement this plan
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Or use the `/double-check` prompt template.
|
|
106
|
+
|
|
107
|
+
### Manual Activation
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
/review-start
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Activates review mode and immediately sends the review prompt.
|
|
114
|
+
|
|
115
|
+
### Check Status
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
/review-status
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Shows whether review mode is active and current iteration.
|
|
122
|
+
|
|
123
|
+
### Exit Early
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
/review-exit
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Or just type something else. Any non-trigger input exits review mode.
|
|
130
|
+
|
|
131
|
+
### Adjust Iterations
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
/review-max 5
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Changes max iterations for current session.
|
|
138
|
+
|
|
139
|
+
## Configuration
|
|
140
|
+
|
|
141
|
+
Configure in `~/.pi/agent/settings.json`. Works out of the box, but everything is customizable:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"reviewerLoop": {
|
|
146
|
+
"maxIterations": 7,
|
|
147
|
+
"reviewPrompt": "template:double-check",
|
|
148
|
+
"triggerPatterns": {
|
|
149
|
+
"mode": "extend",
|
|
150
|
+
"patterns": ["execute the plan"]
|
|
151
|
+
},
|
|
152
|
+
"exitPatterns": {
|
|
153
|
+
"mode": "extend",
|
|
154
|
+
"patterns": ["ship it", "ready to merge"]
|
|
155
|
+
},
|
|
156
|
+
"issuesFixedPatterns": {
|
|
157
|
+
"mode": "extend",
|
|
158
|
+
"patterns": ["addressed the following"]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Options
|
|
165
|
+
|
|
166
|
+
| Option | Description |
|
|
167
|
+
|--------|-------------|
|
|
168
|
+
| `maxIterations` | Max review prompts before auto-exit (default: 7) |
|
|
169
|
+
| `reviewPrompt` | The prompt to send each iteration |
|
|
170
|
+
| `triggerPatterns` | What activates review mode |
|
|
171
|
+
| `exitPatterns` | What indicates "review complete" |
|
|
172
|
+
| `issuesFixedPatterns` | What indicates issues were fixed (prevents false exits) |
|
|
173
|
+
|
|
174
|
+
### Review Prompt Sources
|
|
175
|
+
|
|
176
|
+
Three formats for `reviewPrompt`:
|
|
177
|
+
|
|
178
|
+
| Format | Example | Description |
|
|
179
|
+
|--------|---------|-------------|
|
|
180
|
+
| Template | `"template:double-check"` | Loads `~/.pi/agent/prompts/double-check.md` |
|
|
181
|
+
| File | `"~/prompts/review.md"` | Loads from any file path |
|
|
182
|
+
| Inline | `"Review the code carefully..."` | Uses text directly |
|
|
183
|
+
|
|
184
|
+
Templates and files reload on each use. Edit them and changes take effect immediately.
|
|
185
|
+
|
|
186
|
+
### Pattern Configuration
|
|
187
|
+
|
|
188
|
+
Each pattern setting accepts:
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"mode": "extend",
|
|
193
|
+
"patterns": ["simple string", "/regex\\s+pattern/i"]
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Modes:**
|
|
198
|
+
- `"extend"` (default, recommended) - Add your patterns to the built-in defaults
|
|
199
|
+
- `"replace"` - Use only your patterns, discard defaults entirely
|
|
200
|
+
|
|
201
|
+
**Pattern formats:**
|
|
202
|
+
- Simple string → auto-escaped, case-insensitive literal match
|
|
203
|
+
- `/pattern/flags` → full regex with custom flags
|
|
204
|
+
|
|
205
|
+
**Why extend mode is recommended:** The built-in defaults use sophisticated regex patterns to handle edge cases (e.g., distinguishing "No issues found" from "Issues found and fixed"). If you use replace mode, you take full responsibility for handling these nuances. Extend mode lets you add simple patterns like `"ship it"` while the defaults handle the tricky stuff.
|
|
206
|
+
|
|
207
|
+
## Default Patterns
|
|
208
|
+
|
|
209
|
+
These are the built-ins (all customizable):
|
|
210
|
+
|
|
211
|
+
**Triggers:**
|
|
212
|
+
- "implement plan/spec", "implement the plan/spec"
|
|
213
|
+
- "start implementing", "let's implement", "go ahead and implement"
|
|
214
|
+
- `/double-check` template content
|
|
215
|
+
|
|
216
|
+
**Exit phrases:**
|
|
217
|
+
- "no issues found", "no bugs found"
|
|
218
|
+
- "looks good", "all good" (on own line)
|
|
219
|
+
|
|
220
|
+
**Issues-fixed indicators:**
|
|
221
|
+
- "fixed the following", "issues fixed", "bugs fixed"
|
|
222
|
+
- "Issues:", "Bugs:", "Changes:" (headers)
|
|
223
|
+
- "ready for another review"
|
|
224
|
+
|
|
225
|
+
## Exit Conditions
|
|
226
|
+
|
|
227
|
+
The loop exits when:
|
|
228
|
+
|
|
229
|
+
1. **Exit phrase without fixes** - Agent says "no issues" and didn't fix anything
|
|
230
|
+
2. **Max iterations** - Safety limit reached (default: 7)
|
|
231
|
+
3. **User interrupts** - You type something that isn't a trigger
|
|
232
|
+
4. **Manual exit** - `/review-exit` command
|
|
233
|
+
5. **Abort** - Press ESC or agent response is empty
|
|
234
|
+
|
|
235
|
+
## Commands
|
|
236
|
+
|
|
237
|
+
| Command | Description |
|
|
238
|
+
|---------|-------------|
|
|
239
|
+
| `/review-start` | Activate and send review prompt immediately |
|
|
240
|
+
| `/review-exit` | Exit review mode |
|
|
241
|
+
| `/review-max <n>` | Set max iterations (session only) |
|
|
242
|
+
| `/review-status` | Show current state |
|
|
243
|
+
|
|
244
|
+
## Tool API
|
|
245
|
+
|
|
246
|
+
The `review_loop` tool lets the agent programmatically control review mode:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Check status (default)
|
|
250
|
+
review_loop({})
|
|
251
|
+
|
|
252
|
+
// Start review mode
|
|
253
|
+
review_loop({ start: true })
|
|
254
|
+
|
|
255
|
+
// Start with custom max iterations
|
|
256
|
+
review_loop({ start: true, maxIterations: 5 })
|
|
257
|
+
|
|
258
|
+
// Stop review mode
|
|
259
|
+
review_loop({ stop: true })
|
|
260
|
+
|
|
261
|
+
// Just update max iterations
|
|
262
|
+
review_loop({ maxIterations: 10 })
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Returns:**
|
|
266
|
+
```json
|
|
267
|
+
{
|
|
268
|
+
"active": true,
|
|
269
|
+
"currentIteration": 2,
|
|
270
|
+
"maxIterations": 7,
|
|
271
|
+
"message": "Review mode active: iteration 2/7"
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Mode priority:** `start` > `stop` > status (default)
|
|
276
|
+
|
|
277
|
+
## How It Works
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
input event
|
|
281
|
+
↓
|
|
282
|
+
matches trigger? → enter review mode
|
|
283
|
+
↓
|
|
284
|
+
agent responds
|
|
285
|
+
↓
|
|
286
|
+
agent_end event
|
|
287
|
+
↓
|
|
288
|
+
matches exit + no fixes? → exit review mode
|
|
289
|
+
↓
|
|
290
|
+
iteration < max? → send review prompt → loop
|
|
291
|
+
↓
|
|
292
|
+
otherwise → exit (max reached)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Events used:**
|
|
296
|
+
- `session_start` - Reload settings
|
|
297
|
+
- `input` - Detect triggers, handle interrupts
|
|
298
|
+
- `before_agent_start` - Check expanded prompts for triggers
|
|
299
|
+
- `agent_end` - Analyze response, decide to loop or exit
|
|
300
|
+
|
|
301
|
+
## Limitations
|
|
302
|
+
|
|
303
|
+
- **User templates only** - `template:name` loads from `~/.pi/agent/prompts/`, not project templates
|
|
304
|
+
- **Session-scoped /review-max** - Doesn't persist across sessions
|
|
305
|
+
- **Pattern failures are silent** - Invalid regex patterns are skipped without error
|
|
306
|
+
|
|
307
|
+
## File Structure
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
pi-review-loop/
|
|
311
|
+
├── index.ts # Extension entry, event handlers, commands
|
|
312
|
+
├── settings.ts # Configuration loading, pattern parsing, defaults
|
|
313
|
+
├── scripts/
|
|
314
|
+
│ ├── install.js # npm postinstall - copies extension + prompts
|
|
315
|
+
│ └── uninstall.js # npm preuninstall - removes extension
|
|
316
|
+
├── prompts/
|
|
317
|
+
│ ├── double-check.md # Review code template
|
|
318
|
+
│ └── double-check-plan.md # Review plan template
|
|
319
|
+
├── package.json
|
|
320
|
+
├── review-loop.png # Banner image
|
|
321
|
+
├── README.md
|
|
322
|
+
├── CHANGELOG.md
|
|
323
|
+
└── LICENSE
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Credits
|
|
327
|
+
|
|
328
|
+
Inspired by and built on ideas from:
|
|
329
|
+
|
|
330
|
+
- **[Ralph Wiggum Loop](https://ghuntley.com/ralph/)** by [@GeoffreyHuntley](https://x.com/GeoffreyHuntley) - Loosely based on the "Ralph" loop
|
|
331
|
+
- **["Fresh eyes" review prompt](https://x.com/doodlestein/status/1956228999945806049)** by [@doodlestein](https://x.com/doodlestein)
|
|
332
|
+
- **[pi](https://github.com/badlogic/pi-mono/)** by [@badlogicgames](https://x.com/badlogicgames) — The agent framework powering all of this
|
package/index.ts
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { loadSettings, getReviewPrompt, type ReviewerLoopSettings } from "./settings.js";
|
|
4
|
+
|
|
5
|
+
export default function (pi: ExtensionAPI) {
|
|
6
|
+
let settings: ReviewerLoopSettings = loadSettings();
|
|
7
|
+
let reviewModeActive = false;
|
|
8
|
+
let currentIteration = 0;
|
|
9
|
+
|
|
10
|
+
function updateStatus(ctx: ExtensionContext) {
|
|
11
|
+
if (reviewModeActive) {
|
|
12
|
+
ctx.ui.setStatus(
|
|
13
|
+
"review-loop",
|
|
14
|
+
`Review mode (${currentIteration}/${settings.maxIterations})`
|
|
15
|
+
);
|
|
16
|
+
} else {
|
|
17
|
+
ctx.ui.setStatus("review-loop", undefined);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function exitReviewMode(ctx: ExtensionContext, reason: string) {
|
|
22
|
+
reviewModeActive = false;
|
|
23
|
+
currentIteration = 0;
|
|
24
|
+
updateStatus(ctx);
|
|
25
|
+
ctx.ui.notify(`Review mode ended: ${reason}`, "info");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function enterReviewMode(ctx: ExtensionContext) {
|
|
29
|
+
reviewModeActive = true;
|
|
30
|
+
currentIteration = 0;
|
|
31
|
+
updateStatus(ctx);
|
|
32
|
+
ctx.ui.notify("Review mode activated", "info");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pi.on("session_start", async () => {
|
|
36
|
+
settings = loadSettings();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
pi.on("input", async (event, ctx) => {
|
|
40
|
+
if (!ctx.hasUI) return { action: "continue" as const };
|
|
41
|
+
|
|
42
|
+
const isTrigger = settings.triggerPatterns.some((p) => p.test(event.text));
|
|
43
|
+
|
|
44
|
+
if (reviewModeActive && event.source === "interactive" && !isTrigger) {
|
|
45
|
+
exitReviewMode(ctx, "user interrupted");
|
|
46
|
+
return { action: "continue" as const };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (isTrigger && !reviewModeActive) {
|
|
50
|
+
enterReviewMode(ctx);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { action: "continue" as const };
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
57
|
+
if (!ctx.hasUI) return;
|
|
58
|
+
if (reviewModeActive) return;
|
|
59
|
+
|
|
60
|
+
const isTrigger = settings.triggerPatterns.some((p) => p.test(event.prompt));
|
|
61
|
+
if (isTrigger) {
|
|
62
|
+
enterReviewMode(ctx);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
pi.on("agent_end", async (event, ctx) => {
|
|
67
|
+
if (!ctx.hasUI) return;
|
|
68
|
+
if (!reviewModeActive) return;
|
|
69
|
+
|
|
70
|
+
const assistantMessages = event.messages.filter((m) => m.role === "assistant");
|
|
71
|
+
const lastAssistantMessage = assistantMessages[assistantMessages.length - 1];
|
|
72
|
+
|
|
73
|
+
if (!lastAssistantMessage) {
|
|
74
|
+
exitReviewMode(ctx, "aborted");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const textContent = lastAssistantMessage.content
|
|
79
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
80
|
+
.map((c) => c.text)
|
|
81
|
+
.join("\n");
|
|
82
|
+
|
|
83
|
+
if (!textContent.trim()) {
|
|
84
|
+
exitReviewMode(ctx, "aborted");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const hasExitPhrase = settings.exitPatterns.some((p) => p.test(textContent));
|
|
89
|
+
const hasIssuesFixed = settings.issuesFixedPatterns.some((p) =>
|
|
90
|
+
p.test(textContent)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (hasExitPhrase && !hasIssuesFixed) {
|
|
94
|
+
exitReviewMode(ctx, "no issues found");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
currentIteration++;
|
|
99
|
+
if (currentIteration > settings.maxIterations) {
|
|
100
|
+
exitReviewMode(ctx, `max iterations (${settings.maxIterations}) reached`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
updateStatus(ctx);
|
|
105
|
+
pi.sendUserMessage(getReviewPrompt(settings.reviewPromptConfig), {
|
|
106
|
+
deliverAs: "followUp",
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
pi.registerCommand("review-start", {
|
|
111
|
+
description: "Activate review loop and send review prompt immediately",
|
|
112
|
+
handler: async (_args, ctx) => {
|
|
113
|
+
if (reviewModeActive) {
|
|
114
|
+
ctx.ui.notify("Review mode is already active", "info");
|
|
115
|
+
} else {
|
|
116
|
+
enterReviewMode(ctx);
|
|
117
|
+
pi.sendUserMessage(getReviewPrompt(settings.reviewPromptConfig));
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
pi.registerCommand("review-max", {
|
|
123
|
+
description: "Set max review iterations (default: 7)",
|
|
124
|
+
handler: async (args, ctx) => {
|
|
125
|
+
const num = parseInt(args, 10);
|
|
126
|
+
if (isNaN(num) || num < 1) {
|
|
127
|
+
ctx.ui.notify("Usage: /review-max <number>", "error");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
settings.maxIterations = num;
|
|
131
|
+
ctx.ui.notify(`Max review iterations set to ${settings.maxIterations}`, "info");
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
pi.registerCommand("review-exit", {
|
|
136
|
+
description: "Exit review mode manually",
|
|
137
|
+
handler: async (_args, ctx) => {
|
|
138
|
+
if (reviewModeActive) {
|
|
139
|
+
exitReviewMode(ctx, "manual exit");
|
|
140
|
+
} else {
|
|
141
|
+
ctx.ui.notify("Review mode is not active", "info");
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
pi.registerCommand("review-status", {
|
|
147
|
+
description: "Show review mode status",
|
|
148
|
+
handler: async (_args, ctx) => {
|
|
149
|
+
if (reviewModeActive) {
|
|
150
|
+
ctx.ui.notify(
|
|
151
|
+
`Review mode active: iteration ${currentIteration}/${settings.maxIterations}`,
|
|
152
|
+
"info"
|
|
153
|
+
);
|
|
154
|
+
} else {
|
|
155
|
+
ctx.ui.notify(
|
|
156
|
+
`Review mode inactive (max: ${settings.maxIterations})`,
|
|
157
|
+
"info"
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
pi.registerTool({
|
|
164
|
+
name: "review_loop",
|
|
165
|
+
description:
|
|
166
|
+
"Control the automated code review loop. Start/stop review mode or check status. When started, the loop repeatedly prompts for code review until 'No issues found' or max iterations reached.",
|
|
167
|
+
parameters: Type.Object({
|
|
168
|
+
start: Type.Optional(
|
|
169
|
+
Type.Boolean({
|
|
170
|
+
description: "Start review mode and send the review prompt",
|
|
171
|
+
})
|
|
172
|
+
),
|
|
173
|
+
stop: Type.Optional(
|
|
174
|
+
Type.Boolean({
|
|
175
|
+
description: "Stop review mode",
|
|
176
|
+
})
|
|
177
|
+
),
|
|
178
|
+
maxIterations: Type.Optional(
|
|
179
|
+
Type.Number({
|
|
180
|
+
description: "Set max iterations (can be combined with start)",
|
|
181
|
+
minimum: 1,
|
|
182
|
+
})
|
|
183
|
+
),
|
|
184
|
+
}),
|
|
185
|
+
|
|
186
|
+
async execute(_toolCallId, params, _onUpdate, ctx) {
|
|
187
|
+
// Update maxIterations if provided
|
|
188
|
+
if (typeof params.maxIterations === "number" && params.maxIterations >= 1) {
|
|
189
|
+
settings.maxIterations = params.maxIterations;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Mode: start > stop > status
|
|
193
|
+
if (params.start) {
|
|
194
|
+
if (reviewModeActive) {
|
|
195
|
+
return {
|
|
196
|
+
content: [
|
|
197
|
+
{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: JSON.stringify({
|
|
200
|
+
active: true,
|
|
201
|
+
currentIteration,
|
|
202
|
+
maxIterations: settings.maxIterations,
|
|
203
|
+
message: "Review mode is already active",
|
|
204
|
+
}),
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
enterReviewMode(ctx);
|
|
211
|
+
pi.sendUserMessage(getReviewPrompt(settings.reviewPromptConfig));
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
content: [
|
|
215
|
+
{
|
|
216
|
+
type: "text",
|
|
217
|
+
text: JSON.stringify({
|
|
218
|
+
active: true,
|
|
219
|
+
currentIteration,
|
|
220
|
+
maxIterations: settings.maxIterations,
|
|
221
|
+
message: "Review mode started. Review prompt sent.",
|
|
222
|
+
}),
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (params.stop) {
|
|
229
|
+
if (!reviewModeActive) {
|
|
230
|
+
return {
|
|
231
|
+
content: [
|
|
232
|
+
{
|
|
233
|
+
type: "text",
|
|
234
|
+
text: JSON.stringify({
|
|
235
|
+
active: false,
|
|
236
|
+
currentIteration: 0,
|
|
237
|
+
maxIterations: settings.maxIterations,
|
|
238
|
+
message: "Review mode is not active",
|
|
239
|
+
}),
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
exitReviewMode(ctx, "stopped by agent");
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
content: [
|
|
249
|
+
{
|
|
250
|
+
type: "text",
|
|
251
|
+
text: JSON.stringify({
|
|
252
|
+
active: false,
|
|
253
|
+
currentIteration: 0,
|
|
254
|
+
maxIterations: settings.maxIterations,
|
|
255
|
+
message: "Review mode stopped",
|
|
256
|
+
}),
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Default: status
|
|
263
|
+
return {
|
|
264
|
+
content: [
|
|
265
|
+
{
|
|
266
|
+
type: "text",
|
|
267
|
+
text: JSON.stringify({
|
|
268
|
+
active: reviewModeActive,
|
|
269
|
+
currentIteration,
|
|
270
|
+
maxIterations: settings.maxIterations,
|
|
271
|
+
message: reviewModeActive
|
|
272
|
+
? `Review mode active: iteration ${currentIteration}/${settings.maxIterations}`
|
|
273
|
+
: "Review mode inactive",
|
|
274
|
+
}),
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
};
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-review-loop",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Automated code review loop extension for pi coding agent",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"postinstall": "node scripts/install.js",
|
|
7
|
+
"preuninstall": "node scripts/uninstall.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.ts",
|
|
11
|
+
"settings.ts",
|
|
12
|
+
"review-loop.png",
|
|
13
|
+
"scripts/",
|
|
14
|
+
"prompts/"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pi",
|
|
18
|
+
"pi-coding-agent",
|
|
19
|
+
"extension",
|
|
20
|
+
"code-review",
|
|
21
|
+
"automation",
|
|
22
|
+
"ralph-wiggum-loop"
|
|
23
|
+
],
|
|
24
|
+
"author": "Nico Bailon",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/nicobailon/pi-review-loop.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/nicobailon/pi-review-loop/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/nicobailon/pi-review-loop#readme"
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Double check / critique implementation plan with fresh eyes
|
|
3
|
+
---
|
|
4
|
+
Great, now I want you to carefully read over the plan/spec and compare against the codebase with "fresh eyes," looking super carefully for any obvious bugs, errors, problems, issues, confusion, etc. Also double check that the patterns and coding styles match existing codebase conventions and preferences. Consider whether there is any unnecessary complexity or duplication, or if the changes can be more minimal without changing scope of requirements and without causing regressions or increasing the risk of bugs. Also keep in mind that this is not an MVP - so don't skip anything. If any issues are found, proceed to fix them without being asked to do so.
|
|
5
|
+
|
|
6
|
+
**Response format:**
|
|
7
|
+
- If you find ANY issues: fix them, then list what you fixed. Do NOT say "no issues found" - instead end with "Fixed [N] issue(s). Ready for another review."
|
|
8
|
+
- If you find ZERO issues: respond with exactly "No issues found."
|
|
9
|
+
|
|
10
|
+
$@
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Double check / review implementation with fresh eyes
|
|
3
|
+
---
|
|
4
|
+
Great, now I want you to carefully read over all of the new code you just wrote and other existing code with "fresh eyes," looking super carefully for any obvious bugs, errors, problems, issues, confusion, etc. Also, if you notice any pre-existing issues/bugs those should be addressed.
|
|
5
|
+
|
|
6
|
+
**Response format:**
|
|
7
|
+
- If you find ANY issues: fix them, then list what you fixed. Do NOT say "no issues found" - instead end with "Fixed [N] issue(s). Ready for another review."
|
|
8
|
+
- If you find ZERO issues: respond with exactly "No issues found."
|
|
9
|
+
|
|
10
|
+
$@
|
package/review-loop.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
const EXTENSION_NAME = "pi-review-loop";
|
|
8
|
+
const TARGET_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", EXTENSION_NAME);
|
|
9
|
+
const PROMPTS_DIR = path.join(os.homedir(), ".pi", "agent", "prompts");
|
|
10
|
+
const SOURCE_DIR = path.dirname(__dirname);
|
|
11
|
+
const FILES = ["index.ts", "settings.ts", "review-loop.png"];
|
|
12
|
+
const PROMPTS = ["double-check.md", "double-check-plan.md"];
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// Install extension files
|
|
16
|
+
fs.mkdirSync(TARGET_DIR, { recursive: true });
|
|
17
|
+
|
|
18
|
+
for (const file of FILES) {
|
|
19
|
+
const src = path.join(SOURCE_DIR, file);
|
|
20
|
+
const dest = path.join(TARGET_DIR, file);
|
|
21
|
+
|
|
22
|
+
if (fs.existsSync(src)) {
|
|
23
|
+
fs.copyFileSync(src, dest);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Install prompt templates (skip if already exist)
|
|
28
|
+
fs.mkdirSync(PROMPTS_DIR, { recursive: true });
|
|
29
|
+
const installedPrompts = [];
|
|
30
|
+
|
|
31
|
+
for (const file of PROMPTS) {
|
|
32
|
+
const src = path.join(SOURCE_DIR, "prompts", file);
|
|
33
|
+
const dest = path.join(PROMPTS_DIR, file);
|
|
34
|
+
|
|
35
|
+
if (fs.existsSync(src) && !fs.existsSync(dest)) {
|
|
36
|
+
fs.copyFileSync(src, dest);
|
|
37
|
+
installedPrompts.push(file);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log("");
|
|
42
|
+
console.log("✓ pi-review-loop installed to ~/.pi/agent/extensions/pi-review-loop/");
|
|
43
|
+
if (installedPrompts.length > 0) {
|
|
44
|
+
console.log(`✓ Prompt templates installed: ${installedPrompts.join(", ")}`);
|
|
45
|
+
}
|
|
46
|
+
console.log(" Restart pi to load the extension.");
|
|
47
|
+
console.log("");
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error("Failed to install pi-review-loop:", err.message);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
const EXTENSION_NAME = "pi-review-loop";
|
|
8
|
+
const TARGET_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", EXTENSION_NAME);
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
if (fs.existsSync(TARGET_DIR)) {
|
|
12
|
+
fs.rmSync(TARGET_DIR, { recursive: true });
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log("✓ pi-review-loop removed from ~/.pi/agent/extensions/pi-review-loop/");
|
|
15
|
+
console.log("");
|
|
16
|
+
}
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error("Failed to uninstall pi-review-loop:", err.message);
|
|
19
|
+
}
|
package/settings.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
export const SETTINGS_PATH = join(homedir(), ".pi", "agent", "settings.json");
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_MAX_ITERATIONS = 7;
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_REVIEW_PROMPT = `Great, now I want you to carefully read over all of the new code you just wrote and other existing code you just modified with "fresh eyes," looking super carefully for any obvious bugs, errors, problems, issues, confusion, etc. If any issues are found, proceed to fix them without being asked to do so.
|
|
10
|
+
|
|
11
|
+
**Response format:**
|
|
12
|
+
- If you find ANY issues: fix them, then list what you fixed. Do NOT say "no issues found" - instead end with "Fixed [N] issue(s). Ready for another review."
|
|
13
|
+
- If you find ZERO issues: respond with exactly "No issues found."`;
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_TRIGGER_PATTERNS: RegExp[] = [
|
|
16
|
+
/\bimplement\s+(the\s+)?plan\b/i,
|
|
17
|
+
/\bimplement\s+(the\s+)?spec\b/i,
|
|
18
|
+
/\bimplement\s+(this\s+)?plan\b/i,
|
|
19
|
+
/\bimplement\s+(this\s+)?spec\b/i,
|
|
20
|
+
/\bstart\s+implementing\b.*\b(plan|spec)\b/i,
|
|
21
|
+
/\bgo\s+ahead\s+and\s+implement\b.*\b(plan|spec)\b/i,
|
|
22
|
+
/\blet'?s\s+implement\b.*\b(plan|spec)\b/i,
|
|
23
|
+
/\b(plan|spec)\b.*\bstart\s+implementing\b/i,
|
|
24
|
+
/\b(plan|spec)\b.*\bgo\s+ahead\s+and\s+implement\b/i,
|
|
25
|
+
/\b(plan|spec)\b.*\blet'?s\s+implement\b/i,
|
|
26
|
+
/read over all of the new code.*fresh eyes/i,
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_EXIT_PATTERNS: RegExp[] = [
|
|
30
|
+
/no\s+(\w+\s+)?issues\s+found/i,
|
|
31
|
+
/no\s+(\w+\s+)?bugs\s+found/i,
|
|
32
|
+
/(?:^|\n)\s*(?:looks\s+good|all\s+good)[\s.,!]*(?:$|\n)/im,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export const DEFAULT_ISSUES_FIXED_PATTERNS: RegExp[] = [
|
|
36
|
+
/issues?\s+(i\s+)?fixed/i,
|
|
37
|
+
/fixed\s+(the\s+)?(following|these|this|issues?|bugs?)/i,
|
|
38
|
+
/fixed\s+\d+\s+issues?/i,
|
|
39
|
+
/found\s+and\s+(fixed|corrected|resolved)/i,
|
|
40
|
+
/bugs?\s+(i\s+)?fixed/i,
|
|
41
|
+
/corrected\s+(the\s+)?(following|these|this)/i,
|
|
42
|
+
/(?<!no\s)issues?\s+(i\s+)?(found|identified|discovered)/i,
|
|
43
|
+
/(?<!no\s)problems?\s+(i\s+)?(found|identified|discovered)/i,
|
|
44
|
+
/changes?\s+(i\s+)?made/i,
|
|
45
|
+
/here'?s?\s+what\s+(i\s+)?(fixed|changed|corrected)/i,
|
|
46
|
+
/(issues|bugs|problems|changes|fixes)\s*:/i,
|
|
47
|
+
/ready\s+for\s+(another|the\s+next)\s+review/i,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export interface PatternConfig {
|
|
51
|
+
mode?: "extend" | "replace";
|
|
52
|
+
patterns: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ReviewPromptConfig {
|
|
56
|
+
type: "inline" | "file" | "template";
|
|
57
|
+
value: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ReviewerLoopSettingsRaw {
|
|
61
|
+
maxIterations?: number;
|
|
62
|
+
reviewPrompt?: string;
|
|
63
|
+
triggerPatterns?: PatternConfig;
|
|
64
|
+
exitPatterns?: PatternConfig;
|
|
65
|
+
issuesFixedPatterns?: PatternConfig;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ReviewerLoopSettings {
|
|
69
|
+
maxIterations: number;
|
|
70
|
+
reviewPromptConfig: ReviewPromptConfig;
|
|
71
|
+
triggerPatterns: RegExp[];
|
|
72
|
+
exitPatterns: RegExp[];
|
|
73
|
+
issuesFixedPatterns: RegExp[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parsePattern(input: unknown): RegExp | null {
|
|
77
|
+
if (typeof input !== "string") return null;
|
|
78
|
+
|
|
79
|
+
const match = input.match(/^\/(.+)\/([gimsuy]*)$/);
|
|
80
|
+
if (match) {
|
|
81
|
+
try {
|
|
82
|
+
return new RegExp(match[1], match[2]);
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const escaped = input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
88
|
+
return new RegExp(escaped, "i");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function loadPatterns(
|
|
92
|
+
config: PatternConfig | undefined,
|
|
93
|
+
defaults: RegExp[]
|
|
94
|
+
): RegExp[] {
|
|
95
|
+
if (!config?.patterns || !Array.isArray(config.patterns) || config.patterns.length === 0) {
|
|
96
|
+
return defaults;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const userPatterns = config.patterns
|
|
100
|
+
.map(parsePattern)
|
|
101
|
+
.filter((p): p is RegExp => p !== null);
|
|
102
|
+
|
|
103
|
+
if (config.mode === "replace") {
|
|
104
|
+
return userPatterns.length > 0 ? userPatterns : defaults;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return [...defaults, ...userPatterns];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isFilePath(value: string): boolean {
|
|
111
|
+
return (
|
|
112
|
+
value.startsWith("~/") ||
|
|
113
|
+
value.startsWith("/") ||
|
|
114
|
+
value.startsWith("./") ||
|
|
115
|
+
value.endsWith(".md") ||
|
|
116
|
+
value.endsWith(".txt")
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseReviewPromptConfig(value: string | undefined): ReviewPromptConfig {
|
|
121
|
+
if (!value) {
|
|
122
|
+
return { type: "inline", value: DEFAULT_REVIEW_PROMPT };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (value.startsWith("template:")) {
|
|
126
|
+
const templateName = value.slice("template:".length).trim();
|
|
127
|
+
return { type: "template", value: templateName };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (isFilePath(value)) {
|
|
131
|
+
return { type: "file", value };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { type: "inline", value };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function stripFrontmatter(content: string): string {
|
|
138
|
+
const match = content.match(/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n)?([\s\S]*)$/);
|
|
139
|
+
return match ? match[1].trim() : content.trim();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function resolvePath(value: string): string {
|
|
143
|
+
if (value.startsWith("~/")) {
|
|
144
|
+
return join(homedir(), value.slice(2));
|
|
145
|
+
}
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function getReviewPrompt(config: ReviewPromptConfig): string {
|
|
150
|
+
switch (config.type) {
|
|
151
|
+
case "inline":
|
|
152
|
+
return config.value || DEFAULT_REVIEW_PROMPT;
|
|
153
|
+
|
|
154
|
+
case "file": {
|
|
155
|
+
const resolvedPath = resolvePath(config.value);
|
|
156
|
+
try {
|
|
157
|
+
const content = readFileSync(resolvedPath, "utf-8").trim();
|
|
158
|
+
return content || DEFAULT_REVIEW_PROMPT;
|
|
159
|
+
} catch {
|
|
160
|
+
return DEFAULT_REVIEW_PROMPT;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case "template": {
|
|
165
|
+
const templatePath = join(
|
|
166
|
+
homedir(),
|
|
167
|
+
".pi",
|
|
168
|
+
"agent",
|
|
169
|
+
"prompts",
|
|
170
|
+
`${config.value}.md`
|
|
171
|
+
);
|
|
172
|
+
try {
|
|
173
|
+
if (!existsSync(templatePath)) {
|
|
174
|
+
return DEFAULT_REVIEW_PROMPT;
|
|
175
|
+
}
|
|
176
|
+
let content = readFileSync(templatePath, "utf-8");
|
|
177
|
+
content = stripFrontmatter(content);
|
|
178
|
+
content = content.replace(/\$@/g, "").trim();
|
|
179
|
+
return content || DEFAULT_REVIEW_PROMPT;
|
|
180
|
+
} catch {
|
|
181
|
+
return DEFAULT_REVIEW_PROMPT;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function loadSettings(): ReviewerLoopSettings {
|
|
188
|
+
let raw: ReviewerLoopSettingsRaw = {};
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const content = readFileSync(SETTINGS_PATH, "utf-8");
|
|
192
|
+
const parsed = JSON.parse(content);
|
|
193
|
+
raw = parsed?.reviewerLoop ?? {};
|
|
194
|
+
} catch {
|
|
195
|
+
// Use all defaults
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
maxIterations:
|
|
200
|
+
typeof raw.maxIterations === "number" && raw.maxIterations >= 1
|
|
201
|
+
? raw.maxIterations
|
|
202
|
+
: DEFAULT_MAX_ITERATIONS,
|
|
203
|
+
reviewPromptConfig: parseReviewPromptConfig(raw.reviewPrompt),
|
|
204
|
+
triggerPatterns: loadPatterns(raw.triggerPatterns, DEFAULT_TRIGGER_PATTERNS),
|
|
205
|
+
exitPatterns: loadPatterns(raw.exitPatterns, DEFAULT_EXIT_PATTERNS),
|
|
206
|
+
issuesFixedPatterns: loadPatterns(
|
|
207
|
+
raw.issuesFixedPatterns,
|
|
208
|
+
DEFAULT_ISSUES_FIXED_PATTERNS
|
|
209
|
+
),
|
|
210
|
+
};
|
|
211
|
+
}
|