pi-review 1.0.0 → 1.1.0
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 +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +62 -2
- package/dist/lib/child-session.d.ts +1 -1
- package/dist/lib/child-session.d.ts.map +1 -1
- package/dist/lib/child-session.js +2 -1
- package/dist/lib/conversation-context.d.ts +1 -0
- package/dist/lib/conversation-context.d.ts.map +1 -1
- package/dist/lib/conversation-context.js +10 -0
- package/package.json +1 -1
- package/src/index.ts +79 -3
- package/src/lib/child-session.ts +2 -0
- package/src/lib/conversation-context.ts +11 -0
package/README.md
CHANGED
|
@@ -36,6 +36,12 @@ Add optional focus text:
|
|
|
36
36
|
/review focus on release safety and backward compatibility
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
After the review finishes, return to the reviewed branch and prefill the editor with the review findings:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
/review-back
|
|
43
|
+
```
|
|
44
|
+
|
|
39
45
|
## How it works
|
|
40
46
|
|
|
41
47
|
1. Waits for the current agent turn to finish if needed
|
|
@@ -44,6 +50,7 @@ Add optional focus text:
|
|
|
44
50
|
4. Creates a new branch from the current conversation
|
|
45
51
|
5. Sends a maintainer-style review prompt with optional focus text
|
|
46
52
|
6. Restores your previous thinking level when the review turn ends
|
|
53
|
+
7. `/review-back` returns to the reviewed branch and puts the review findings in the editor
|
|
47
54
|
|
|
48
55
|
## Review output
|
|
49
56
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,+BAA+B,CAAC;AAqFhF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,QAmFvD"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { sendMessageInNewBranch } from "./lib/child-session.js";
|
|
2
|
-
import { extractConversation, formatConversation } from "./lib/conversation-context.js";
|
|
2
|
+
import { extractConversation, extractLatestAssistantText, formatConversation, } from "./lib/conversation-context.js";
|
|
3
3
|
const REVIEW_THINKING_LEVEL = "high";
|
|
4
|
+
const REVIEW_METADATA_TYPE = "pi-review";
|
|
4
5
|
const REVIEW_INSTRUCTION = `Review the available work and context.
|
|
5
6
|
Put your strict maintainer hat on.
|
|
6
7
|
Find concrete, high-confidence, material issues introduced by the work or revealed by the additional context.
|
|
8
|
+
Do not stop after the first few findings; keep reviewing until you have checked the full diff/context, then do one final pass specifically for issues you may have missed.
|
|
9
|
+
Verify completeness against the stated task, requirements, and acceptance criteria; flag missing or partially implemented requirements as findings.
|
|
7
10
|
Focus on correctness, security, performance, operability, and maintainability.
|
|
8
11
|
Do not speculate; point to the affected behavior, invariant, or code path.
|
|
9
12
|
Prefer issues the author would likely fix before merge.
|
|
@@ -33,6 +36,30 @@ function buildReviewMessage(args, conversationXml) {
|
|
|
33
36
|
reviewInstruction,
|
|
34
37
|
].join("\n");
|
|
35
38
|
}
|
|
39
|
+
function isReviewMetadata(data) {
|
|
40
|
+
return (!!data &&
|
|
41
|
+
typeof data === "object" &&
|
|
42
|
+
"kind" in data &&
|
|
43
|
+
data.kind === "review" &&
|
|
44
|
+
"reviewedLeafId" in data &&
|
|
45
|
+
typeof data.reviewedLeafId === "string");
|
|
46
|
+
}
|
|
47
|
+
function findReviewMetadata(branch) {
|
|
48
|
+
for (const entry of [...branch].reverse()) {
|
|
49
|
+
if (entry.type !== "custom" || entry.customType !== REVIEW_METADATA_TYPE)
|
|
50
|
+
continue;
|
|
51
|
+
if (isReviewMetadata(entry.data))
|
|
52
|
+
return entry.data;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
function buildReviewBackEditorText(reviewReport) {
|
|
57
|
+
return [
|
|
58
|
+
"<review_findings>",
|
|
59
|
+
reviewReport.trim(),
|
|
60
|
+
"</review_findings>",
|
|
61
|
+
].join("\n");
|
|
62
|
+
}
|
|
36
63
|
export default function reviewExtension(pi) {
|
|
37
64
|
let originalThinkingLevel;
|
|
38
65
|
function restoreThinkingLevel() {
|
|
@@ -51,6 +78,7 @@ export default function reviewExtension(pi) {
|
|
|
51
78
|
await ctx.waitForIdle();
|
|
52
79
|
}
|
|
53
80
|
const branch = ctx.sessionManager.getBranch();
|
|
81
|
+
const reviewedLeafId = ctx.sessionManager.getLeafId();
|
|
54
82
|
const extractedConversation = extractConversation(branch);
|
|
55
83
|
const conversationXml = extractedConversation.length === 0 ? undefined : formatConversation(extractedConversation);
|
|
56
84
|
const reviewMessage = buildReviewMessage(args, conversationXml);
|
|
@@ -61,7 +89,11 @@ export default function reviewExtension(pi) {
|
|
|
61
89
|
}
|
|
62
90
|
let started = false;
|
|
63
91
|
try {
|
|
64
|
-
started = await sendMessageInNewBranch(pi, ctx, branch, reviewMessage, "review")
|
|
92
|
+
started = await sendMessageInNewBranch(pi, ctx, branch, reviewMessage, "review", () => {
|
|
93
|
+
if (!reviewedLeafId)
|
|
94
|
+
return;
|
|
95
|
+
pi.appendEntry(REVIEW_METADATA_TYPE, { kind: "review", reviewedLeafId });
|
|
96
|
+
});
|
|
65
97
|
}
|
|
66
98
|
finally {
|
|
67
99
|
if (!started)
|
|
@@ -74,4 +106,32 @@ export default function reviewExtension(pi) {
|
|
|
74
106
|
}
|
|
75
107
|
},
|
|
76
108
|
});
|
|
109
|
+
pi.registerCommand("review-back", {
|
|
110
|
+
description: "Return to reviewed branch with review findings in the editor",
|
|
111
|
+
handler: async (_args, ctx) => {
|
|
112
|
+
if (!ctx.isIdle()) {
|
|
113
|
+
await ctx.waitForIdle();
|
|
114
|
+
}
|
|
115
|
+
if (!ctx.hasUI)
|
|
116
|
+
return;
|
|
117
|
+
const branch = ctx.sessionManager.getBranch();
|
|
118
|
+
const metadata = findReviewMetadata(branch);
|
|
119
|
+
if (!metadata) {
|
|
120
|
+
ctx.ui.notify("No review branch metadata found", "warning");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const reviewReport = extractLatestAssistantText(branch);
|
|
124
|
+
if (!reviewReport) {
|
|
125
|
+
ctx.ui.notify("No assistant review report found", "warning");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const result = await ctx.navigateTree(metadata.reviewedLeafId, { summarize: false });
|
|
129
|
+
if (result.cancelled) {
|
|
130
|
+
ctx.ui.notify("Return to reviewed branch cancelled", "info");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
ctx.ui.setEditorText(buildReviewBackEditorText(reviewReport));
|
|
134
|
+
ctx.ui.notify("Returned to reviewed branch", "info");
|
|
135
|
+
},
|
|
136
|
+
});
|
|
77
137
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext, SessionEntry } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
export declare function sendMessageInNewBranch(pi: ExtensionAPI, ctx: ExtensionCommandContext, branch: SessionEntry[], message: string, purpose: string): Promise<boolean>;
|
|
2
|
+
export declare function sendMessageInNewBranch(pi: ExtensionAPI, ctx: ExtensionCommandContext, branch: SessionEntry[], message: string, purpose: string, beforeSend?: () => void): Promise<boolean>;
|
|
3
3
|
//# sourceMappingURL=child-session.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"child-session.d.ts","sourceRoot":"","sources":["../../src/lib/child-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,uBAAuB,EACvB,YAAY,EACb,MAAM,+BAA+B,CAAC;AAgBvC,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE,uBAAuB,EAC5B,MAAM,EAAE,YAAY,EAAE,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"child-session.d.ts","sourceRoot":"","sources":["../../src/lib/child-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,uBAAuB,EACvB,YAAY,EACb,MAAM,+BAA+B,CAAC;AAgBvC,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE,uBAAuB,EAC5B,MAAM,EAAE,YAAY,EAAE,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,IAAI,GACtB,OAAO,CAAC,OAAO,CAAC,CAelB"}
|
|
@@ -9,7 +9,7 @@ function getEmptyBranchTargetId(branch) {
|
|
|
9
9
|
}
|
|
10
10
|
return undefined;
|
|
11
11
|
}
|
|
12
|
-
export async function sendMessageInNewBranch(pi, ctx, branch, message, purpose) {
|
|
12
|
+
export async function sendMessageInNewBranch(pi, ctx, branch, message, purpose, beforeSend) {
|
|
13
13
|
const targetId = getEmptyBranchTargetId(branch);
|
|
14
14
|
if (targetId) {
|
|
15
15
|
const result = await ctx.navigateTree(targetId, { summarize: false });
|
|
@@ -19,6 +19,7 @@ export async function sendMessageInNewBranch(pi, ctx, branch, message, purpose)
|
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
beforeSend?.();
|
|
22
23
|
pi.sendUserMessage(message);
|
|
23
24
|
if (ctx.hasUI)
|
|
24
25
|
ctx.ui.notify(`Started ${purpose} branch`, "info");
|
|
@@ -4,5 +4,6 @@ export type ExtractedMessage = {
|
|
|
4
4
|
text: string;
|
|
5
5
|
};
|
|
6
6
|
export declare function extractConversation(branch: SessionEntry[]): ExtractedMessage[];
|
|
7
|
+
export declare function extractLatestAssistantText(branch: SessionEntry[]): string;
|
|
7
8
|
export declare function formatConversation(messages: ExtractedMessage[]): string;
|
|
8
9
|
//# sourceMappingURL=conversation-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-context.d.ts","sourceRoot":"","sources":["../../src/lib/conversation-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAkCF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAkB9E;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAOvE"}
|
|
1
|
+
{"version":3,"file":"conversation-context.d.ts","sourceRoot":"","sources":["../../src/lib/conversation-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAkCF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAkB9E;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CASzE;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAOvE"}
|
|
@@ -35,6 +35,16 @@ export function extractConversation(branch) {
|
|
|
35
35
|
return [];
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
+
export function extractLatestAssistantText(branch) {
|
|
39
|
+
for (const entry of [...branch].reverse()) {
|
|
40
|
+
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
41
|
+
continue;
|
|
42
|
+
const text = extractText(entry.message.content, false);
|
|
43
|
+
if (text)
|
|
44
|
+
return text;
|
|
45
|
+
}
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
38
48
|
export function formatConversation(messages) {
|
|
39
49
|
return messages
|
|
40
50
|
.map((message, index) => {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, SessionEntry } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import { sendMessageInNewBranch } from "./lib/child-session.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
extractConversation,
|
|
6
|
+
extractLatestAssistantText,
|
|
7
|
+
formatConversation,
|
|
8
|
+
} from "./lib/conversation-context.js";
|
|
5
9
|
|
|
6
10
|
const REVIEW_THINKING_LEVEL = "high";
|
|
11
|
+
const REVIEW_METADATA_TYPE = "pi-review";
|
|
7
12
|
|
|
8
13
|
type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
14
|
+
type ReviewMetadata = {
|
|
15
|
+
kind: "review";
|
|
16
|
+
reviewedLeafId: string;
|
|
17
|
+
};
|
|
9
18
|
|
|
10
19
|
const REVIEW_INSTRUCTION = `Review the available work and context.
|
|
11
20
|
Put your strict maintainer hat on.
|
|
12
21
|
Find concrete, high-confidence, material issues introduced by the work or revealed by the additional context.
|
|
22
|
+
Do not stop after the first few findings; keep reviewing until you have checked the full diff/context, then do one final pass specifically for issues you may have missed.
|
|
23
|
+
Verify completeness against the stated task, requirements, and acceptance criteria; flag missing or partially implemented requirements as findings.
|
|
13
24
|
Focus on correctness, security, performance, operability, and maintainability.
|
|
14
25
|
Do not speculate; point to the affected behavior, invariant, or code path.
|
|
15
26
|
Prefer issues the author would likely fix before merge.
|
|
@@ -44,6 +55,34 @@ function buildReviewMessage(args: string, conversationXml?: string): string {
|
|
|
44
55
|
].join("\n");
|
|
45
56
|
}
|
|
46
57
|
|
|
58
|
+
function isReviewMetadata(data: unknown): data is ReviewMetadata {
|
|
59
|
+
return (
|
|
60
|
+
!!data &&
|
|
61
|
+
typeof data === "object" &&
|
|
62
|
+
"kind" in data &&
|
|
63
|
+
data.kind === "review" &&
|
|
64
|
+
"reviewedLeafId" in data &&
|
|
65
|
+
typeof data.reviewedLeafId === "string"
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findReviewMetadata(branch: SessionEntry[]): ReviewMetadata | undefined {
|
|
70
|
+
for (const entry of [...branch].reverse()) {
|
|
71
|
+
if (entry.type !== "custom" || entry.customType !== REVIEW_METADATA_TYPE) continue;
|
|
72
|
+
if (isReviewMetadata(entry.data)) return entry.data;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildReviewBackEditorText(reviewReport: string): string {
|
|
79
|
+
return [
|
|
80
|
+
"<review_findings>",
|
|
81
|
+
reviewReport.trim(),
|
|
82
|
+
"</review_findings>",
|
|
83
|
+
].join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
47
86
|
export default function reviewExtension(pi: ExtensionAPI) {
|
|
48
87
|
let originalThinkingLevel: ThinkingLevel | undefined;
|
|
49
88
|
|
|
@@ -66,6 +105,7 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
66
105
|
}
|
|
67
106
|
|
|
68
107
|
const branch = ctx.sessionManager.getBranch();
|
|
108
|
+
const reviewedLeafId = ctx.sessionManager.getLeafId();
|
|
69
109
|
const extractedConversation = extractConversation(branch);
|
|
70
110
|
const conversationXml =
|
|
71
111
|
extractedConversation.length === 0 ? undefined : formatConversation(extractedConversation);
|
|
@@ -79,7 +119,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
79
119
|
|
|
80
120
|
let started = false;
|
|
81
121
|
try {
|
|
82
|
-
started = await sendMessageInNewBranch(pi, ctx, branch, reviewMessage, "review")
|
|
122
|
+
started = await sendMessageInNewBranch(pi, ctx, branch, reviewMessage, "review", () => {
|
|
123
|
+
if (!reviewedLeafId) return;
|
|
124
|
+
pi.appendEntry(REVIEW_METADATA_TYPE, { kind: "review", reviewedLeafId });
|
|
125
|
+
});
|
|
83
126
|
} finally {
|
|
84
127
|
if (!started) restoreThinkingLevel();
|
|
85
128
|
}
|
|
@@ -90,4 +133,37 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
90
133
|
}
|
|
91
134
|
},
|
|
92
135
|
});
|
|
136
|
+
|
|
137
|
+
pi.registerCommand("review-back", {
|
|
138
|
+
description: "Return to reviewed branch with review findings in the editor",
|
|
139
|
+
handler: async (_args, ctx) => {
|
|
140
|
+
if (!ctx.isIdle()) {
|
|
141
|
+
await ctx.waitForIdle();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!ctx.hasUI) return;
|
|
145
|
+
|
|
146
|
+
const branch = ctx.sessionManager.getBranch();
|
|
147
|
+
const metadata = findReviewMetadata(branch);
|
|
148
|
+
if (!metadata) {
|
|
149
|
+
ctx.ui.notify("No review branch metadata found", "warning");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const reviewReport = extractLatestAssistantText(branch);
|
|
154
|
+
if (!reviewReport) {
|
|
155
|
+
ctx.ui.notify("No assistant review report found", "warning");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const result = await ctx.navigateTree(metadata.reviewedLeafId, { summarize: false });
|
|
160
|
+
if (result.cancelled) {
|
|
161
|
+
ctx.ui.notify("Return to reviewed branch cancelled", "info");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
ctx.ui.setEditorText(buildReviewBackEditorText(reviewReport));
|
|
166
|
+
ctx.ui.notify("Returned to reviewed branch", "info");
|
|
167
|
+
},
|
|
168
|
+
});
|
|
93
169
|
}
|
package/src/lib/child-session.ts
CHANGED
|
@@ -24,6 +24,7 @@ export async function sendMessageInNewBranch(
|
|
|
24
24
|
branch: SessionEntry[],
|
|
25
25
|
message: string,
|
|
26
26
|
purpose: string,
|
|
27
|
+
beforeSend?: () => void,
|
|
27
28
|
): Promise<boolean> {
|
|
28
29
|
const targetId = getEmptyBranchTargetId(branch);
|
|
29
30
|
if (targetId) {
|
|
@@ -34,6 +35,7 @@ export async function sendMessageInNewBranch(
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
beforeSend?.();
|
|
37
39
|
pi.sendUserMessage(message);
|
|
38
40
|
|
|
39
41
|
if (ctx.hasUI) ctx.ui.notify(`Started ${purpose} branch`, "info");
|
|
@@ -57,6 +57,17 @@ export function extractConversation(branch: SessionEntry[]): ExtractedMessage[]
|
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export function extractLatestAssistantText(branch: SessionEntry[]): string {
|
|
61
|
+
for (const entry of [...branch].reverse()) {
|
|
62
|
+
if (entry.type !== "message" || entry.message.role !== "assistant") continue;
|
|
63
|
+
|
|
64
|
+
const text = extractText(entry.message.content, false);
|
|
65
|
+
if (text) return text;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
export function formatConversation(messages: ExtractedMessage[]): string {
|
|
61
72
|
return messages
|
|
62
73
|
.map((message, index) => {
|