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 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
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA8ClE,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,QA8CvD"}
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,GACd,OAAO,CAAC,OAAO,CAAC,CAclB"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-review",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Review current pi work in a new branch with conversation context",
5
5
  "type": "module",
6
6
  "keywords": [
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 { extractConversation, formatConversation } from "./lib/conversation-context.js";
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
  }
@@ -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) => {