pi-vscode-sr 1.1.10 → 1.4.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/README.md +4 -3
- package/images/logo.psd +0 -0
- package/package.json +4 -4
- package/src/index.ts +0 -362
- package/vscode-ext/dist/extension.js +0 -255
- package/vscode-ext/dist/extension.js.map +0 -1
- package/vscode-ext/dist/types.js +0 -3
- package/vscode-ext/dist/types.js.map +0 -1
- package/vscode-ext/icon.jpg +0 -0
- package/vscode-ext/package-lock.json +0 -59
- package/vscode-ext/package.json +0 -59
- package/vscode-ext/src/extension.ts +0 -250
- package/vscode-ext/src/types.ts +0 -36
- package/vscode-ext/tsconfig.json +0 -16
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Secure code review bridge between **Pi coding agent** and **VS Code**. Every file mutation proposed by Pi opens a diff editor — you preview, edit, and approve or reject before anything touches disk.
|
|
4
4
|
|
|
5
|
-
<img width="800" alt="Pi Defender" src="https://raw.githubusercontent.com/Serhioromano/pi-vscode/refs/heads/main/images/pi-vscode.png">
|
|
5
|
+
<img width="800" alt="Pi Defender" src="https://raw.githubusercontent.com/Serhioromano/pi-vscode-sr/refs/heads/main/images/pi-vscode.png">
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
## ✨ How it works
|
|
@@ -16,6 +16,7 @@ Secure code review bridge between **Pi coding agent** and **VS Code**. Every fil
|
|
|
16
16
|
|--------|--------|
|
|
17
17
|
| ✅ **Approve** | Apply this file's changes |
|
|
18
18
|
| ❌ **Reject** | Discard this file's changes — agent sees an error and must retry |
|
|
19
|
+
| 💭 **Rethink** | Open a text input dialog to give the agent feedback — e.g. «use async/await instead of promise chains». Changes are not applied, agent sees your feedback and can retry with corrections. |
|
|
19
20
|
| ⭐ **Approve All** | Auto-approve every future change for this session |
|
|
20
21
|
| 🚪 **Abort** | Stop the agent session immediately |
|
|
21
22
|
|
|
@@ -26,13 +27,13 @@ You can also approve/reject from the diff tab.
|
|
|
26
27
|
### 1. Pi Extension
|
|
27
28
|
|
|
28
29
|
```bash
|
|
29
|
-
pi install pi-vscode-sr
|
|
30
|
+
pi install npm:pi-vscode-sr
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
Or install locally:
|
|
33
34
|
|
|
34
35
|
```bash
|
|
35
|
-
pi install pi-vscode-sr -l
|
|
36
|
+
pi install npm:pi-vscode-sr -l
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
### 2. VS Code Extension
|
package/images/logo.psd
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-vscode-sr",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Code diff assistant for VS Code.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Serhioromano",
|
|
7
|
-
"repository": "github:Serhioromano/pi-vscode",
|
|
7
|
+
"repository": "github:Serhioromano/pi-vscode-sr",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"postinstall": "mkdir -p ~/.pi"
|
|
10
10
|
},
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"extensions": [
|
|
26
26
|
"./src/index.ts"
|
|
27
27
|
],
|
|
28
|
-
"image": "https://raw.githubusercontent.com/Serhioromano/pi-vscode/refs/heads/main/images/pi-vscode.png"
|
|
28
|
+
"image": "https://raw.githubusercontent.com/Serhioromano/pi-vscode-sr/refs/heads/main/images/pi-vscode.png"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@earendil-works/pi-coding-agent": "^0.74.0",
|
|
32
|
-
"@types/node": "^25.
|
|
32
|
+
"@types/node": "^25.9.3",
|
|
33
33
|
"typescript": "^6.0.3"
|
|
34
34
|
}
|
|
35
35
|
}
|
package/src/index.ts
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { Type } from "typebox";
|
|
4
|
-
import { randomUUID } from "crypto";
|
|
5
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
6
|
-
import { dirname, join, resolve } from "path";
|
|
7
|
-
|
|
8
|
-
// ─── Session state ───────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
const sessionReviewIds = new Set<string>();
|
|
11
|
-
const sessionApproveAll = new Set<string>(); // review IDs auto-approved
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// ─── Helper: create review request and wait for result ───────────────
|
|
15
|
-
|
|
16
|
-
type ReviewOutcome =
|
|
17
|
-
| { status: "approved"; final: string }
|
|
18
|
-
| { status: "rejected" }
|
|
19
|
-
| { status: "timeout" };
|
|
20
|
-
|
|
21
|
-
async function createReviewAndWait(
|
|
22
|
-
ctx: ExtensionContext,
|
|
23
|
-
filePath: string,
|
|
24
|
-
original: string,
|
|
25
|
-
proposed: string,
|
|
26
|
-
description: string,
|
|
27
|
-
): Promise<ReviewOutcome> {
|
|
28
|
-
const uuid = randomUUID();
|
|
29
|
-
const requestsDir = join(ctx.cwd, ".pi", "review-requests");
|
|
30
|
-
const resultsDir = join(ctx.cwd, ".pi", "review-results");
|
|
31
|
-
mkdirSync(requestsDir, { recursive: true });
|
|
32
|
-
mkdirSync(resultsDir, { recursive: true });
|
|
33
|
-
|
|
34
|
-
const reviewRequest = {
|
|
35
|
-
id: uuid,
|
|
36
|
-
title: description,
|
|
37
|
-
files: [{ path: filePath, original, proposed, description }],
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
writeFileSync(join(requestsDir, `${uuid}.json`), JSON.stringify(reviewRequest, null, 2), "utf-8");
|
|
41
|
-
ctx.ui.notify(`📝 Review: ${filePath} — check VS Code diff`, "info");
|
|
42
|
-
|
|
43
|
-
sessionReviewIds.add(uuid);
|
|
44
|
-
|
|
45
|
-
// Check if approve-all was already chosen
|
|
46
|
-
if (sessionApproveAll.size > 0) {
|
|
47
|
-
writeSyncResult(resultsDir, uuid, "approved", proposed);
|
|
48
|
-
return { status: "approved", final: proposed };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Phase 1: give VS Code a head start (2s, poll every 100ms) so TUI doesn't
|
|
52
|
-
// pop up when the user is already reviewing in editor.
|
|
53
|
-
// 2 seconds with 100ms intervals = 20 checks — catches VS Code response quickly.
|
|
54
|
-
const resultPath = join(resultsDir, `${uuid}.json`);
|
|
55
|
-
const deadline = Date.now() + 10 * 60 * 1000;
|
|
56
|
-
|
|
57
|
-
const early = await pollResultFile(resultPath, Date.now() + 2000, 100);
|
|
58
|
-
if (early !== "timeout") {
|
|
59
|
-
if (early === "file-rejected") return { status: "rejected" };
|
|
60
|
-
return { status: "approved", final: proposed };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Sync check: VS Code may have written the result file between poll intervals.
|
|
64
|
-
if (existsSync(resultPath)) {
|
|
65
|
-
const result = JSON.parse(readFileSync(resultPath, "utf-8"));
|
|
66
|
-
if (result.status === "rejected" || result.files?.[0]?.status === "rejected") {
|
|
67
|
-
return { status: "rejected" };
|
|
68
|
-
}
|
|
69
|
-
return { status: "approved", final: proposed };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Phase 2: show TUI and keep polling VS Code in parallel (every 500ms)
|
|
73
|
-
const tuiPromise = showTuiSelector(ctx, filePath);
|
|
74
|
-
const pollPromise = pollResultFile(resultPath, deadline, 500);
|
|
75
|
-
|
|
76
|
-
const outcome = await Promise.race([tuiPromise, pollPromise]);
|
|
77
|
-
|
|
78
|
-
// ── Process outcome (return result, never throw) ──
|
|
79
|
-
|
|
80
|
-
if (outcome === "abort") {
|
|
81
|
-
writeSyncResult(resultsDir, uuid, "rejected");
|
|
82
|
-
ctx.abort();
|
|
83
|
-
return { status: "rejected" };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (outcome === "file-rejected" || outcome === "rejected") {
|
|
87
|
-
return { status: "rejected" };
|
|
88
|
-
}
|
|
89
|
-
if (outcome === "file-approved" || outcome === "approved") {
|
|
90
|
-
return { status: "approved", final: proposed };
|
|
91
|
-
}
|
|
92
|
-
if (outcome === "approve-all") {
|
|
93
|
-
for (const rid of sessionReviewIds) {
|
|
94
|
-
sessionApproveAll.add(rid);
|
|
95
|
-
}
|
|
96
|
-
writeSyncResult(resultsDir, uuid, "approved", proposed);
|
|
97
|
-
return { status: "approved", final: proposed };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return { status: "timeout" };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function writeSyncResult(resultsDir: string, uuid: string, status: "approved" | "rejected", content?: string) {
|
|
104
|
-
writeFileSync(
|
|
105
|
-
join(resultsDir, `${uuid}.json`),
|
|
106
|
-
JSON.stringify(
|
|
107
|
-
{
|
|
108
|
-
id: uuid,
|
|
109
|
-
files: [{ path: "", status, final: content ?? "" }],
|
|
110
|
-
},
|
|
111
|
-
null,
|
|
112
|
-
2,
|
|
113
|
-
),
|
|
114
|
-
"utf-8",
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function pollResultFile(resultPath: string, deadline: number, interval = 500): Promise<string> {
|
|
119
|
-
while (Date.now() < deadline) {
|
|
120
|
-
try {
|
|
121
|
-
if (existsSync(resultPath)) {
|
|
122
|
-
const raw = readFileSync(resultPath, "utf-8");
|
|
123
|
-
if (!raw.trim()) {
|
|
124
|
-
// File exists but is empty — still being written
|
|
125
|
-
await sleep(200);
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
const result = JSON.parse(raw);
|
|
129
|
-
const fileResult = result.files?.[0];
|
|
130
|
-
if (result.status === "rejected" || fileResult?.status === "rejected") {
|
|
131
|
-
return "file-rejected";
|
|
132
|
-
}
|
|
133
|
-
return "file-approved";
|
|
134
|
-
}
|
|
135
|
-
} catch {
|
|
136
|
-
// File may be partially written or malformed — retry
|
|
137
|
-
}
|
|
138
|
-
await sleep(interval);
|
|
139
|
-
}
|
|
140
|
-
return "timeout";
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function showTuiSelector(ctx: ExtensionContext, filePath: string): Promise<string> {
|
|
144
|
-
const choice = await ctx.ui.select(
|
|
145
|
-
`📝 Review: ${filePath}`,
|
|
146
|
-
[
|
|
147
|
-
"✅ Approve",
|
|
148
|
-
"❌ Reject",
|
|
149
|
-
"⭐ Approve All for this session",
|
|
150
|
-
"🚪 Abort",
|
|
151
|
-
],
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
if (!choice) return "timeout";
|
|
155
|
-
if (choice.startsWith("🚪")) return "abort";
|
|
156
|
-
if (choice.startsWith("⭐")) return "approve-all";
|
|
157
|
-
if (choice.startsWith("✅")) return "approved";
|
|
158
|
-
if (choice.startsWith("❌")) return "rejected";
|
|
159
|
-
return "timeout";
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function sleep(ms: number): Promise<void> {
|
|
163
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ─── Apply edits in-memory (mirrors built-in edit logic) ─────────────
|
|
167
|
-
|
|
168
|
-
function applyEdits(content: string, edits: Array<{ oldText: string; newText: string }>): string {
|
|
169
|
-
let result = content;
|
|
170
|
-
for (const edit of edits) {
|
|
171
|
-
const idx = result.indexOf(edit.oldText);
|
|
172
|
-
if (idx === -1) {
|
|
173
|
-
throw new Error(`oldText not found in file`);
|
|
174
|
-
}
|
|
175
|
-
const nextIdx = result.indexOf(edit.oldText, idx + 1);
|
|
176
|
-
if (nextIdx !== -1) {
|
|
177
|
-
throw new Error(`oldText is not unique in file`);
|
|
178
|
-
}
|
|
179
|
-
result = result.replace(edit.oldText, edit.newText);
|
|
180
|
-
}
|
|
181
|
-
return result;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ─── Override `write` ────────────────────────────────────────────────
|
|
185
|
-
|
|
186
|
-
function registerWriteOverride(pi: ExtensionAPI) {
|
|
187
|
-
pi.registerTool({
|
|
188
|
-
name: "write",
|
|
189
|
-
label: "write (with review)",
|
|
190
|
-
description:
|
|
191
|
-
"Write content to a file. Instead of writing directly, creates a review request so the user " +
|
|
192
|
-
"can approve or reject the change in VS Code or directly in the terminal. Returns only after " +
|
|
193
|
-
"the user makes a decision.",
|
|
194
|
-
promptSnippet: "Create or overwrite files with user review",
|
|
195
|
-
promptGuidelines: [
|
|
196
|
-
"Use write for any file creation or complete rewrite — user review is required.",
|
|
197
|
-
"The tool blocks until the user approves or rejects.",
|
|
198
|
-
"If content is identical to existing file, no review is created.",
|
|
199
|
-
],
|
|
200
|
-
parameters: Type.Object({
|
|
201
|
-
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
|
202
|
-
content: Type.String({ description: "Content to write to the file" }),
|
|
203
|
-
}),
|
|
204
|
-
executionMode: "sequential" as const,
|
|
205
|
-
|
|
206
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
207
|
-
const absolutePath = resolve(ctx.cwd, params.path);
|
|
208
|
-
let original = "";
|
|
209
|
-
let fileExists = false;
|
|
210
|
-
try {
|
|
211
|
-
original = readFileSync(absolutePath, "utf-8");
|
|
212
|
-
fileExists = true;
|
|
213
|
-
} catch {
|
|
214
|
-
// new file
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (fileExists && original === params.content) {
|
|
218
|
-
return {
|
|
219
|
-
content: [{ type: "text", text: `No changes — ${params.path} content is identical.` }],
|
|
220
|
-
details: { path: params.path, status: "no-change" },
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const description = fileExists ? `Update: ${params.path}` : `Create: ${params.path}`;
|
|
225
|
-
const result = await createReviewAndWait(ctx, params.path, original, params.content, description);
|
|
226
|
-
|
|
227
|
-
switch (result.status) {
|
|
228
|
-
case "timeout":
|
|
229
|
-
return {
|
|
230
|
-
content: [{ type: "text", text: `⏰ Review timed out for ${params.path} (10m)` }],
|
|
231
|
-
details: { path: params.path, status: "timeout" },
|
|
232
|
-
};
|
|
233
|
-
case "approved":
|
|
234
|
-
return withFileMutationQueue(absolutePath, async () => {
|
|
235
|
-
mkdirSync(dirname(absolutePath), { recursive: true });
|
|
236
|
-
writeFileSync(absolutePath, result.final, "utf-8");
|
|
237
|
-
return {
|
|
238
|
-
content: [{ type: "text", text: `✅ ${params.path} — approved (${result.final.length} bytes)` }],
|
|
239
|
-
details: { path: params.path, status: "approved", bytes: result.final.length },
|
|
240
|
-
};
|
|
241
|
-
});
|
|
242
|
-
case "rejected":
|
|
243
|
-
return {
|
|
244
|
-
isError: true,
|
|
245
|
-
content: [{ type: "text", text: `❌ ${params.path} — change REJECTED by user. File was NOT modified.` }],
|
|
246
|
-
details: { path: params.path, status: "rejected" },
|
|
247
|
-
};
|
|
248
|
-
default:
|
|
249
|
-
throw new Error(`Unexpected review status: ${(result as any).status}`);
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// ─── Override `edit` ─────────────────────────────────────────────────
|
|
256
|
-
|
|
257
|
-
function registerEditOverride(pi: ExtensionAPI) {
|
|
258
|
-
pi.registerTool({
|
|
259
|
-
name: "edit",
|
|
260
|
-
label: "edit (with review)",
|
|
261
|
-
description:
|
|
262
|
-
"Edit a file by replacing exact text passages. Instead of editing directly, creates a review " +
|
|
263
|
-
"request so the user can approve or reject in VS Code or directly in the terminal.",
|
|
264
|
-
promptSnippet: "Make targeted edits to existing files with user review",
|
|
265
|
-
promptGuidelines: [
|
|
266
|
-
"Use edit for targeted changes to existing files — user review is required.",
|
|
267
|
-
"The tool blocks until the user approves or rejects.",
|
|
268
|
-
],
|
|
269
|
-
parameters: Type.Object({
|
|
270
|
-
path: Type.String({ description: "Path to the file to edit (relative or absolute)" }),
|
|
271
|
-
edits: Type.Array(
|
|
272
|
-
Type.Object({
|
|
273
|
-
oldText: Type.String({ description: "Exact unique text to replace" }),
|
|
274
|
-
newText: Type.String({ description: "Replacement text" }),
|
|
275
|
-
}),
|
|
276
|
-
{
|
|
277
|
-
description:
|
|
278
|
-
"Targeted replacements. Each oldText must be unique and non-overlapping.",
|
|
279
|
-
},
|
|
280
|
-
),
|
|
281
|
-
}),
|
|
282
|
-
executionMode: "sequential" as const,
|
|
283
|
-
|
|
284
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
285
|
-
const absolutePath = resolve(ctx.cwd, params.path);
|
|
286
|
-
|
|
287
|
-
let original: string;
|
|
288
|
-
try {
|
|
289
|
-
original = readFileSync(absolutePath, "utf-8");
|
|
290
|
-
} catch {
|
|
291
|
-
return {
|
|
292
|
-
content: [{ type: "text", text: `❌ File not found: ${params.path}` }],
|
|
293
|
-
details: { path: params.path, status: "error", error: "not found" },
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Apply edits in-memory to get proposed content
|
|
298
|
-
let proposed: string;
|
|
299
|
-
try {
|
|
300
|
-
proposed = applyEdits(original, params.edits);
|
|
301
|
-
} catch (e: any) {
|
|
302
|
-
return {
|
|
303
|
-
content: [{ type: "text", text: `❌ Edit failed: ${e.message} in ${params.path}` }],
|
|
304
|
-
details: { path: params.path, status: "error", error: e.message },
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (original === proposed) {
|
|
309
|
-
return {
|
|
310
|
-
content: [{ type: "text", text: `No changes — ${params.path} content is identical after edits.` }],
|
|
311
|
-
details: { path: params.path, status: "no-change" },
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const result = await createReviewAndWait(ctx, params.path, original, proposed, `Edit: ${params.path}`);
|
|
316
|
-
|
|
317
|
-
switch (result.status) {
|
|
318
|
-
case "timeout":
|
|
319
|
-
return {
|
|
320
|
-
content: [{ type: "text", text: `⏰ Review timed out for ${params.path} (10m)` }],
|
|
321
|
-
details: { path: params.path, status: "timeout" },
|
|
322
|
-
};
|
|
323
|
-
case "approved":
|
|
324
|
-
return withFileMutationQueue(absolutePath, async () => {
|
|
325
|
-
mkdirSync(dirname(absolutePath), { recursive: true });
|
|
326
|
-
writeFileSync(absolutePath, result.final, "utf-8");
|
|
327
|
-
return {
|
|
328
|
-
content: [{ type: "text", text: `✅ ${params.path} — edit approved (${result.final.length} bytes)` }],
|
|
329
|
-
details: { path: params.path, status: "approved", bytes: result.final.length },
|
|
330
|
-
};
|
|
331
|
-
});
|
|
332
|
-
case "rejected":
|
|
333
|
-
return {
|
|
334
|
-
isError: true,
|
|
335
|
-
content: [{ type: "text", text: `❌ ${params.path} — edit REJECTED by user. File was NOT modified.` }],
|
|
336
|
-
details: { path: params.path, status: "rejected" },
|
|
337
|
-
};
|
|
338
|
-
default:
|
|
339
|
-
throw new Error(`Unexpected review status: ${(result as any).status}`);
|
|
340
|
-
}
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// ─── Extension entry point ───────────────────────────────────────────
|
|
346
|
-
|
|
347
|
-
export default function (pi: ExtensionAPI) {
|
|
348
|
-
// Reset review ID tracking on new session
|
|
349
|
-
pi.on("session_start", () => {
|
|
350
|
-
sessionReviewIds.clear();
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
// Reset Approve All on message boundaries
|
|
354
|
-
const clearApproveAll = () => {
|
|
355
|
-
sessionApproveAll.clear();
|
|
356
|
-
};
|
|
357
|
-
pi.on("message_start", clearApproveAll);
|
|
358
|
-
pi.on("message_end", clearApproveAll);
|
|
359
|
-
|
|
360
|
-
registerWriteOverride(pi);
|
|
361
|
-
registerEditOverride(pi);
|
|
362
|
-
}
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.activate = activate;
|
|
37
|
-
exports.deactivate = deactivate;
|
|
38
|
-
const vscode = __importStar(require("vscode"));
|
|
39
|
-
const fs = __importStar(require("fs"));
|
|
40
|
-
const path = __importStar(require("path"));
|
|
41
|
-
// Global state
|
|
42
|
-
let workspaceRoot;
|
|
43
|
-
let requestsDir;
|
|
44
|
-
let resultsDir;
|
|
45
|
-
let watcher = null;
|
|
46
|
-
// key = tmpFsPath (URI.fsPath of the active editor)
|
|
47
|
-
const sessions = new Map();
|
|
48
|
-
// key = reviewId, value = set of file paths in this review
|
|
49
|
-
const reviewFiles = new Map();
|
|
50
|
-
function activate(context) {
|
|
51
|
-
const root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
|
52
|
-
if (!root) {
|
|
53
|
-
vscode.window.showWarningMessage('Pi Companion: open a workspace first');
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
workspaceRoot = root;
|
|
57
|
-
requestsDir = path.join(workspaceRoot, '.pi', 'review-requests');
|
|
58
|
-
resultsDir = path.join(workspaceRoot, '.pi', 'review-results');
|
|
59
|
-
// Create directories
|
|
60
|
-
fs.mkdirSync(requestsDir, { recursive: true });
|
|
61
|
-
fs.mkdirSync(resultsDir, { recursive: true });
|
|
62
|
-
// Watch for new review requests
|
|
63
|
-
watcher = fs.watch(requestsDir, (_, filename) => {
|
|
64
|
-
if (!filename?.endsWith('.json'))
|
|
65
|
-
return;
|
|
66
|
-
const fp = path.join(requestsDir, filename);
|
|
67
|
-
if (fs.existsSync(fp))
|
|
68
|
-
handleRequest(fp);
|
|
69
|
-
});
|
|
70
|
-
// Recover incomplete reviews
|
|
71
|
-
for (const f of fs.readdirSync(requestsDir)) {
|
|
72
|
-
if (f.endsWith('.json'))
|
|
73
|
-
handleRequest(path.join(requestsDir, f));
|
|
74
|
-
}
|
|
75
|
-
// Commands for editor/title buttons
|
|
76
|
-
context.subscriptions.push(vscode.commands.registerCommand('pi-sr.approveCurrent', () => approveCurrent()), vscode.commands.registerCommand('pi-sr.rejectCurrent', () => rejectCurrent()));
|
|
77
|
-
}
|
|
78
|
-
function deactivate() {
|
|
79
|
-
watcher?.close();
|
|
80
|
-
}
|
|
81
|
-
// ─── Handle new review request ────────────────────────────────────────
|
|
82
|
-
function handleRequest(requestPath) {
|
|
83
|
-
let req;
|
|
84
|
-
try {
|
|
85
|
-
req = JSON.parse(fs.readFileSync(requestPath, 'utf-8'));
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
vscode.window.showErrorMessage(`Pi Review: malformed JSON in ${requestPath}`);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
if (!req.id || !req.files?.length)
|
|
92
|
-
return;
|
|
93
|
-
// Skip if this review is already being processed
|
|
94
|
-
if (reviewFiles.has(req.id))
|
|
95
|
-
return;
|
|
96
|
-
const fileSet = new Set();
|
|
97
|
-
reviewFiles.set(req.id, fileSet);
|
|
98
|
-
// For each file: create tmp, open diff
|
|
99
|
-
req.files.forEach(file => {
|
|
100
|
-
fileSet.add(file.path);
|
|
101
|
-
const tmpDir = path.join(workspaceRoot, '.pi', 'tmp', req.id);
|
|
102
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
103
|
-
const tmpPath = path.join(tmpDir, path.basename(file.path));
|
|
104
|
-
fs.writeFileSync(tmpPath, file.proposed, 'utf-8');
|
|
105
|
-
// Create original file if it doesn't exist
|
|
106
|
-
const origPath = path.join(workspaceRoot, file.path);
|
|
107
|
-
if (!fs.existsSync(origPath)) {
|
|
108
|
-
fs.mkdirSync(path.dirname(origPath), { recursive: true });
|
|
109
|
-
fs.writeFileSync(origPath, file.original || '', 'utf-8');
|
|
110
|
-
}
|
|
111
|
-
const session = {
|
|
112
|
-
reviewId: req.id,
|
|
113
|
-
filePath: file.path,
|
|
114
|
-
originalFsPath: origPath,
|
|
115
|
-
tmpFsPath: tmpPath,
|
|
116
|
-
status: 'pending',
|
|
117
|
-
};
|
|
118
|
-
sessions.set(tmpPath, session);
|
|
119
|
-
// Open diff
|
|
120
|
-
vscode.commands.executeCommand('vscode.diff', vscode.Uri.file(origPath), vscode.Uri.file(tmpPath), `Pi: ${file.path}`).then(() => {
|
|
121
|
-
vscode.commands.executeCommand('setContext', 'piSr.isActive', true);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
// ─── Approve / Reject ─────────────────────────────────────────────────
|
|
126
|
-
function getCurrentSession() {
|
|
127
|
-
// Tier 1: active editor (fast path — works when tmp side has focus)
|
|
128
|
-
const active = vscode.window.activeTextEditor;
|
|
129
|
-
if (active) {
|
|
130
|
-
const s = sessions.get(active.document.uri.fsPath);
|
|
131
|
-
if (s)
|
|
132
|
-
return s;
|
|
133
|
-
}
|
|
134
|
-
// Tier 2: all visible editors (catches original side of diff)
|
|
135
|
-
for (const editor of vscode.window.visibleTextEditors) {
|
|
136
|
-
const s = sessions.get(editor.document.uri.fsPath);
|
|
137
|
-
if (s)
|
|
138
|
-
return s;
|
|
139
|
-
}
|
|
140
|
-
// Tier 3: if exactly one pending session exists, return it
|
|
141
|
-
// (handles edge case where diff editor sides aren't in visibleTextEditors)
|
|
142
|
-
const pending = [];
|
|
143
|
-
for (const s of sessions.values()) {
|
|
144
|
-
if (s.status === 'pending')
|
|
145
|
-
pending.push(s);
|
|
146
|
-
}
|
|
147
|
-
if (pending.length === 1)
|
|
148
|
-
return pending[0];
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
async function approveCurrent() {
|
|
152
|
-
const s = getCurrentSession();
|
|
153
|
-
if (!s) {
|
|
154
|
-
vscode.window.showErrorMessage('Pi Companion: no review session found. Is the diff editor open?');
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
// Read edited content from tmp file
|
|
158
|
-
const edited = fs.readFileSync(s.tmpFsPath, 'utf-8');
|
|
159
|
-
// Write to original
|
|
160
|
-
fs.writeFileSync(s.originalFsPath, edited, 'utf-8');
|
|
161
|
-
// Remove tmp
|
|
162
|
-
try {
|
|
163
|
-
fs.unlinkSync(s.tmpFsPath);
|
|
164
|
-
}
|
|
165
|
-
catch { }
|
|
166
|
-
s.status = 'approved';
|
|
167
|
-
// Close diff tab
|
|
168
|
-
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
|
|
169
|
-
checkReviewComplete(s.reviewId);
|
|
170
|
-
}
|
|
171
|
-
async function rejectCurrent() {
|
|
172
|
-
const s = getCurrentSession();
|
|
173
|
-
if (!s) {
|
|
174
|
-
vscode.window.showErrorMessage('Pi Companion: no review session found. Is the diff editor open?');
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
// Remove tmp
|
|
178
|
-
try {
|
|
179
|
-
fs.unlinkSync(s.tmpFsPath);
|
|
180
|
-
}
|
|
181
|
-
catch { }
|
|
182
|
-
s.status = 'rejected';
|
|
183
|
-
// Close diff tab
|
|
184
|
-
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
|
|
185
|
-
checkReviewComplete(s.reviewId);
|
|
186
|
-
}
|
|
187
|
-
// ─── Complete review ──────────────────────────────────────────────────
|
|
188
|
-
function checkReviewComplete(reviewId) {
|
|
189
|
-
// Any pending sessions left for this review?
|
|
190
|
-
for (const s of sessions.values()) {
|
|
191
|
-
if (s.reviewId === reviewId && s.status === 'pending')
|
|
192
|
-
return; // not done yet
|
|
193
|
-
}
|
|
194
|
-
// All files processed — build result
|
|
195
|
-
const files = [];
|
|
196
|
-
const fileSet = reviewFiles.get(reviewId);
|
|
197
|
-
if (!fileSet)
|
|
198
|
-
return;
|
|
199
|
-
let allApproved = true;
|
|
200
|
-
let processed = false;
|
|
201
|
-
for (const fp of fileSet) {
|
|
202
|
-
const session = [...sessions.values()].find(s => s.filePath === fp);
|
|
203
|
-
// Pending — shouldn't happen (checked above), but guard anyway
|
|
204
|
-
if (session?.status === 'pending')
|
|
205
|
-
continue;
|
|
206
|
-
let status;
|
|
207
|
-
let final = '';
|
|
208
|
-
if (session?.status === 'rejected') {
|
|
209
|
-
status = 'rejected';
|
|
210
|
-
}
|
|
211
|
-
else if (session?.status === 'approved') {
|
|
212
|
-
status = 'approved';
|
|
213
|
-
final = fs.readFileSync(path.join(workspaceRoot, fp), 'utf-8');
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
// Session went missing — fallback. Safer to treat as rejected.
|
|
217
|
-
console.error(`[Pi Companion] checkReviewComplete: session not found for ${fp} in review ${reviewId}`);
|
|
218
|
-
status = 'rejected';
|
|
219
|
-
}
|
|
220
|
-
files.push({ path: fp, status, final });
|
|
221
|
-
processed = true;
|
|
222
|
-
if (status !== 'approved')
|
|
223
|
-
allApproved = false;
|
|
224
|
-
}
|
|
225
|
-
// Clean up sessions for this review
|
|
226
|
-
for (const [key, s] of sessions) {
|
|
227
|
-
if (s.reviewId === reviewId)
|
|
228
|
-
sessions.delete(key);
|
|
229
|
-
}
|
|
230
|
-
const result = {
|
|
231
|
-
id: reviewId,
|
|
232
|
-
status: !processed ? 'rejected' : allApproved ? 'approved' : 'rejected',
|
|
233
|
-
files,
|
|
234
|
-
};
|
|
235
|
-
// Write result
|
|
236
|
-
const resultPath = path.join(resultsDir, `${reviewId}.json`);
|
|
237
|
-
fs.writeFileSync(resultPath, JSON.stringify(result, null, 2), 'utf-8');
|
|
238
|
-
// Remove request file
|
|
239
|
-
const requestPath = path.join(requestsDir, `${reviewId}.json`);
|
|
240
|
-
try {
|
|
241
|
-
fs.unlinkSync(requestPath);
|
|
242
|
-
}
|
|
243
|
-
catch { }
|
|
244
|
-
// Clean up tmp directory
|
|
245
|
-
const tmpDir = path.join(workspaceRoot, '.pi', 'tmp', reviewId);
|
|
246
|
-
try {
|
|
247
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
248
|
-
}
|
|
249
|
-
catch { }
|
|
250
|
-
// Reset context
|
|
251
|
-
vscode.commands.executeCommand('setContext', 'piSr.isActive', false);
|
|
252
|
-
reviewFiles.delete(reviewId);
|
|
253
|
-
vscode.window.showInformationMessage(`Pi Companion: ${result.status === 'approved' ? 'accepted' : 'rejected'} (${files.filter(f => f.status === 'approved').length}/${files.length})`);
|
|
254
|
-
}
|
|
255
|
-
//# sourceMappingURL=extension.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
|
package/vscode-ext/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/vscode-ext/icon.jpg
DELETED
|
Binary file
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-vscode-review",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"lockfileVersion": 3,
|
|
5
|
-
"requires": true,
|
|
6
|
-
"packages": {
|
|
7
|
-
"": {
|
|
8
|
-
"name": "pi-vscode-review",
|
|
9
|
-
"version": "0.1.0",
|
|
10
|
-
"license": "MIT",
|
|
11
|
-
"devDependencies": {
|
|
12
|
-
"@types/node": "^25.9.2",
|
|
13
|
-
"@types/vscode": "^1.82.0",
|
|
14
|
-
"typescript": "^5.3.0"
|
|
15
|
-
},
|
|
16
|
-
"engines": {
|
|
17
|
-
"vscode": "^1.82.0"
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"node_modules/@types/node": {
|
|
21
|
-
"version": "25.9.2",
|
|
22
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz",
|
|
23
|
-
"integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==",
|
|
24
|
-
"dev": true,
|
|
25
|
-
"license": "MIT",
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"undici-types": ">=7.24.0 <7.24.7"
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"node_modules/@types/vscode": {
|
|
31
|
-
"version": "1.120.0",
|
|
32
|
-
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.120.0.tgz",
|
|
33
|
-
"integrity": "sha512-feaT4Rst+FkTch5zz/ZbNCxoIvo55YU80Be2kiL7OJcod4+CUYf2lUBPdIJzozNnSEMq1VRTGrWEcCGFB3fBmA==",
|
|
34
|
-
"dev": true,
|
|
35
|
-
"license": "MIT"
|
|
36
|
-
},
|
|
37
|
-
"node_modules/typescript": {
|
|
38
|
-
"version": "5.9.3",
|
|
39
|
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
40
|
-
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
|
41
|
-
"dev": true,
|
|
42
|
-
"license": "Apache-2.0",
|
|
43
|
-
"bin": {
|
|
44
|
-
"tsc": "bin/tsc",
|
|
45
|
-
"tsserver": "bin/tsserver"
|
|
46
|
-
},
|
|
47
|
-
"engines": {
|
|
48
|
-
"node": ">=14.17"
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
"node_modules/undici-types": {
|
|
52
|
-
"version": "7.24.6",
|
|
53
|
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
|
54
|
-
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
|
55
|
-
"dev": true,
|
|
56
|
-
"license": "MIT"
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
package/vscode-ext/package.json
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "vscode-pi-sr",
|
|
3
|
-
"displayName": "Pi Agent Companion",
|
|
4
|
-
"description": "Review and approve file changes proposed by Pi agent",
|
|
5
|
-
"version": "1.1.9",
|
|
6
|
-
"publisher": "Serhioromano",
|
|
7
|
-
"icon": "icon.jpg",
|
|
8
|
-
"license": "MIT",
|
|
9
|
-
"repository": "github:Serhioromano/pi-vscode",
|
|
10
|
-
"engines": {
|
|
11
|
-
"vscode": "^1.82.0"
|
|
12
|
-
},
|
|
13
|
-
"categories": [
|
|
14
|
-
"Other"
|
|
15
|
-
],
|
|
16
|
-
"activationEvents": [
|
|
17
|
-
"onStartupFinished"
|
|
18
|
-
],
|
|
19
|
-
"main": "./dist/extension.js",
|
|
20
|
-
"contributes": {
|
|
21
|
-
"commands": [
|
|
22
|
-
{
|
|
23
|
-
"command": "pi-sr.approveCurrent",
|
|
24
|
-
"title": "Pi SR: Accept",
|
|
25
|
-
"icon": "$(check)"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"command": "pi-sr.rejectCurrent",
|
|
29
|
-
"title": "Pi SR: Reject",
|
|
30
|
-
"icon": "$(close)"
|
|
31
|
-
}
|
|
32
|
-
],
|
|
33
|
-
"menus": {
|
|
34
|
-
"editor/title": [
|
|
35
|
-
{
|
|
36
|
-
"command": "pi-sr.approveCurrent",
|
|
37
|
-
"when": "piSr.isActive",
|
|
38
|
-
"group": "navigation@1"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"command": "pi-sr.rejectCurrent",
|
|
42
|
-
"when": "piSr.isActive",
|
|
43
|
-
"group": "navigation@2"
|
|
44
|
-
}
|
|
45
|
-
]
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
"scripts": {
|
|
49
|
-
"compile": "tsc -p tsconfig.json",
|
|
50
|
-
"watch": "tsc -watch -p tsconfig.json",
|
|
51
|
-
"package": "npx @vscode/vsce package"
|
|
52
|
-
},
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"@types/node": "^25.9.2",
|
|
55
|
-
"@types/vscode": "^1.82.0",
|
|
56
|
-
"@vscode/vsce": "^3.2.0",
|
|
57
|
-
"typescript": "^5.3.0"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import * as vscode from 'vscode';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { ReviewRequest, ReviewResult, ReviewResultFile, DiffSession } from './types';
|
|
5
|
-
|
|
6
|
-
// Global state
|
|
7
|
-
let workspaceRoot: string;
|
|
8
|
-
let requestsDir: string;
|
|
9
|
-
let resultsDir: string;
|
|
10
|
-
let watcher: fs.FSWatcher | null = null;
|
|
11
|
-
|
|
12
|
-
// key = tmpFsPath (URI.fsPath of the active editor)
|
|
13
|
-
const sessions = new Map<string, DiffSession>();
|
|
14
|
-
// key = reviewId, value = set of file paths in this review
|
|
15
|
-
const reviewFiles = new Map<string, Set<string>>();
|
|
16
|
-
|
|
17
|
-
export function activate(context: vscode.ExtensionContext) {
|
|
18
|
-
const root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
|
19
|
-
if (!root) {
|
|
20
|
-
vscode.window.showWarningMessage('Pi Companion: open a workspace first');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
workspaceRoot = root;
|
|
24
|
-
requestsDir = path.join(workspaceRoot, '.pi', 'review-requests');
|
|
25
|
-
resultsDir = path.join(workspaceRoot, '.pi', 'review-results');
|
|
26
|
-
|
|
27
|
-
// Create directories
|
|
28
|
-
fs.mkdirSync(requestsDir, { recursive: true });
|
|
29
|
-
fs.mkdirSync(resultsDir, { recursive: true });
|
|
30
|
-
|
|
31
|
-
// Watch for new review requests
|
|
32
|
-
watcher = fs.watch(requestsDir, (_, filename) => {
|
|
33
|
-
if (!filename?.endsWith('.json')) return;
|
|
34
|
-
const fp = path.join(requestsDir, filename);
|
|
35
|
-
if (fs.existsSync(fp)) handleRequest(fp);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Recover incomplete reviews
|
|
39
|
-
for (const f of fs.readdirSync(requestsDir)) {
|
|
40
|
-
if (f.endsWith('.json')) handleRequest(path.join(requestsDir, f));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Commands for editor/title buttons
|
|
44
|
-
context.subscriptions.push(
|
|
45
|
-
vscode.commands.registerCommand('pi-sr.approveCurrent', () => approveCurrent()),
|
|
46
|
-
vscode.commands.registerCommand('pi-sr.rejectCurrent', () => rejectCurrent()),
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function deactivate() {
|
|
51
|
-
watcher?.close();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ─── Handle new review request ────────────────────────────────────────
|
|
55
|
-
|
|
56
|
-
function handleRequest(requestPath: string) {
|
|
57
|
-
let req: ReviewRequest;
|
|
58
|
-
try {
|
|
59
|
-
req = JSON.parse(fs.readFileSync(requestPath, 'utf-8'));
|
|
60
|
-
} catch {
|
|
61
|
-
vscode.window.showErrorMessage(`Pi Review: malformed JSON in ${requestPath}`);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!req.id || !req.files?.length) return;
|
|
66
|
-
|
|
67
|
-
// Skip if this review is already being processed
|
|
68
|
-
if (reviewFiles.has(req.id)) return;
|
|
69
|
-
|
|
70
|
-
const fileSet = new Set<string>();
|
|
71
|
-
reviewFiles.set(req.id, fileSet);
|
|
72
|
-
|
|
73
|
-
// For each file: create tmp, open diff
|
|
74
|
-
req.files.forEach(file => {
|
|
75
|
-
fileSet.add(file.path);
|
|
76
|
-
|
|
77
|
-
const tmpDir = path.join(workspaceRoot, '.pi', 'tmp', req.id);
|
|
78
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
79
|
-
|
|
80
|
-
const tmpPath = path.join(tmpDir, path.basename(file.path));
|
|
81
|
-
fs.writeFileSync(tmpPath, file.proposed, 'utf-8');
|
|
82
|
-
|
|
83
|
-
// Create original file if it doesn't exist
|
|
84
|
-
const origPath = path.join(workspaceRoot, file.path);
|
|
85
|
-
if (!fs.existsSync(origPath)) {
|
|
86
|
-
fs.mkdirSync(path.dirname(origPath), { recursive: true });
|
|
87
|
-
fs.writeFileSync(origPath, file.original || '', 'utf-8');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const session: DiffSession = {
|
|
91
|
-
reviewId: req.id,
|
|
92
|
-
filePath: file.path,
|
|
93
|
-
originalFsPath: origPath,
|
|
94
|
-
tmpFsPath: tmpPath,
|
|
95
|
-
status: 'pending',
|
|
96
|
-
};
|
|
97
|
-
sessions.set(tmpPath, session);
|
|
98
|
-
|
|
99
|
-
// Open diff
|
|
100
|
-
vscode.commands.executeCommand(
|
|
101
|
-
'vscode.diff',
|
|
102
|
-
vscode.Uri.file(origPath),
|
|
103
|
-
vscode.Uri.file(tmpPath),
|
|
104
|
-
`Pi: ${file.path}`
|
|
105
|
-
).then(() => {
|
|
106
|
-
vscode.commands.executeCommand('setContext', 'piSr.isActive', true);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ─── Approve / Reject ─────────────────────────────────────────────────
|
|
112
|
-
|
|
113
|
-
function getCurrentSession(): DiffSession | undefined {
|
|
114
|
-
// Tier 1: active editor (fast path — works when tmp side has focus)
|
|
115
|
-
const active = vscode.window.activeTextEditor;
|
|
116
|
-
if (active) {
|
|
117
|
-
const s = sessions.get(active.document.uri.fsPath);
|
|
118
|
-
if (s) return s;
|
|
119
|
-
}
|
|
120
|
-
// Tier 2: all visible editors (catches original side of diff)
|
|
121
|
-
for (const editor of vscode.window.visibleTextEditors) {
|
|
122
|
-
const s = sessions.get(editor.document.uri.fsPath);
|
|
123
|
-
if (s) return s;
|
|
124
|
-
}
|
|
125
|
-
// Tier 3: if exactly one pending session exists, return it
|
|
126
|
-
// (handles edge case where diff editor sides aren't in visibleTextEditors)
|
|
127
|
-
const pending: DiffSession[] = [];
|
|
128
|
-
for (const s of sessions.values()) {
|
|
129
|
-
if (s.status === 'pending') pending.push(s);
|
|
130
|
-
}
|
|
131
|
-
if (pending.length === 1) return pending[0];
|
|
132
|
-
return undefined;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async function approveCurrent() {
|
|
136
|
-
const s = getCurrentSession();
|
|
137
|
-
if (!s) {
|
|
138
|
-
vscode.window.showErrorMessage('Pi Companion: no review session found. Is the diff editor open?');
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Read edited content from tmp file
|
|
143
|
-
const edited = fs.readFileSync(s.tmpFsPath, 'utf-8');
|
|
144
|
-
|
|
145
|
-
// Write to original
|
|
146
|
-
fs.writeFileSync(s.originalFsPath, edited, 'utf-8');
|
|
147
|
-
|
|
148
|
-
// Remove tmp
|
|
149
|
-
try { fs.unlinkSync(s.tmpFsPath); } catch {}
|
|
150
|
-
|
|
151
|
-
s.status = 'approved';
|
|
152
|
-
|
|
153
|
-
// Close diff tab
|
|
154
|
-
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
|
|
155
|
-
|
|
156
|
-
checkReviewComplete(s.reviewId);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function rejectCurrent() {
|
|
160
|
-
const s = getCurrentSession();
|
|
161
|
-
if (!s) {
|
|
162
|
-
vscode.window.showErrorMessage('Pi Companion: no review session found. Is the diff editor open?');
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Remove tmp
|
|
167
|
-
try { fs.unlinkSync(s.tmpFsPath); } catch {}
|
|
168
|
-
|
|
169
|
-
s.status = 'rejected';
|
|
170
|
-
|
|
171
|
-
// Close diff tab
|
|
172
|
-
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
|
|
173
|
-
|
|
174
|
-
checkReviewComplete(s.reviewId);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ─── Complete review ──────────────────────────────────────────────────
|
|
178
|
-
|
|
179
|
-
function checkReviewComplete(reviewId: string) {
|
|
180
|
-
// Any pending sessions left for this review?
|
|
181
|
-
for (const s of sessions.values()) {
|
|
182
|
-
if (s.reviewId === reviewId && s.status === 'pending') return; // not done yet
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// All files processed — build result
|
|
186
|
-
const files: ReviewResultFile[] = [];
|
|
187
|
-
const fileSet = reviewFiles.get(reviewId);
|
|
188
|
-
if (!fileSet) return;
|
|
189
|
-
|
|
190
|
-
let allApproved = true;
|
|
191
|
-
let processed = false;
|
|
192
|
-
|
|
193
|
-
for (const fp of fileSet) {
|
|
194
|
-
const session = [...sessions.values()].find(s => s.filePath === fp);
|
|
195
|
-
|
|
196
|
-
// Pending — shouldn't happen (checked above), but guard anyway
|
|
197
|
-
if (session?.status === 'pending') continue;
|
|
198
|
-
|
|
199
|
-
let status: 'approved' | 'rejected';
|
|
200
|
-
let final = '';
|
|
201
|
-
|
|
202
|
-
if (session?.status === 'rejected') {
|
|
203
|
-
status = 'rejected';
|
|
204
|
-
} else if (session?.status === 'approved') {
|
|
205
|
-
status = 'approved';
|
|
206
|
-
final = fs.readFileSync(path.join(workspaceRoot, fp), 'utf-8');
|
|
207
|
-
} else {
|
|
208
|
-
// Session went missing — fallback. Safer to treat as rejected.
|
|
209
|
-
console.error(`[Pi Companion] checkReviewComplete: session not found for ${fp} in review ${reviewId}`);
|
|
210
|
-
status = 'rejected';
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
files.push({ path: fp, status, final });
|
|
214
|
-
processed = true;
|
|
215
|
-
|
|
216
|
-
if (status !== 'approved') allApproved = false;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Clean up sessions for this review
|
|
220
|
-
for (const [key, s] of sessions) {
|
|
221
|
-
if (s.reviewId === reviewId) sessions.delete(key);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const result: ReviewResult = {
|
|
225
|
-
id: reviewId,
|
|
226
|
-
status: !processed ? 'rejected' : allApproved ? 'approved' : 'rejected',
|
|
227
|
-
files,
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
// Write result
|
|
231
|
-
const resultPath = path.join(resultsDir, `${reviewId}.json`);
|
|
232
|
-
fs.writeFileSync(resultPath, JSON.stringify(result, null, 2), 'utf-8');
|
|
233
|
-
|
|
234
|
-
// Remove request file
|
|
235
|
-
const requestPath = path.join(requestsDir, `${reviewId}.json`);
|
|
236
|
-
try { fs.unlinkSync(requestPath); } catch {}
|
|
237
|
-
|
|
238
|
-
// Clean up tmp directory
|
|
239
|
-
const tmpDir = path.join(workspaceRoot, '.pi', 'tmp', reviewId);
|
|
240
|
-
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
241
|
-
|
|
242
|
-
// Reset context
|
|
243
|
-
vscode.commands.executeCommand('setContext', 'piSr.isActive', false);
|
|
244
|
-
|
|
245
|
-
reviewFiles.delete(reviewId);
|
|
246
|
-
|
|
247
|
-
vscode.window.showInformationMessage(
|
|
248
|
-
`Pi Companion: ${result.status === 'approved' ? 'accepted' : 'rejected'} (${files.filter(f => f.status === 'approved').length}/${files.length})`
|
|
249
|
-
);
|
|
250
|
-
}
|
package/vscode-ext/src/types.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export interface ReviewFile {
|
|
2
|
-
path: string;
|
|
3
|
-
original: string;
|
|
4
|
-
proposed: string;
|
|
5
|
-
description?: string;
|
|
6
|
-
language?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ReviewRequest {
|
|
10
|
-
id: string;
|
|
11
|
-
title: string;
|
|
12
|
-
files: ReviewFile[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type FileStatus = 'pending' | 'approved' | 'rejected';
|
|
16
|
-
|
|
17
|
-
export interface ReviewResultFile {
|
|
18
|
-
path: string;
|
|
19
|
-
status: 'approved' | 'rejected';
|
|
20
|
-
final: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ReviewResult {
|
|
24
|
-
id: string;
|
|
25
|
-
status: 'approved' | 'rejected';
|
|
26
|
-
files: ReviewResultFile[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Внутреннее состояние одного файла в ревью */
|
|
30
|
-
export interface DiffSession {
|
|
31
|
-
reviewId: string;
|
|
32
|
-
filePath: string;
|
|
33
|
-
originalFsPath: string; // путь к оригинальному файлу на диске
|
|
34
|
-
tmpFsPath: string; // путь к временному файлу с proposed-контентом
|
|
35
|
-
status: FileStatus;
|
|
36
|
-
}
|
package/vscode-ext/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"module": "commonjs",
|
|
4
|
-
"target": "ES2022",
|
|
5
|
-
"lib": ["ES2022"],
|
|
6
|
-
"outDir": "dist",
|
|
7
|
-
"rootDir": "src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"sourceMap": true
|
|
14
|
-
},
|
|
15
|
-
"include": ["src"]
|
|
16
|
-
}
|