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 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
Binary file
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "pi-vscode-sr",
3
- "version": "1.1.10",
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.7.0",
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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"}
@@ -1,3 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }