@zephyrdeng/pi-review 0.6.2 → 0.7.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.
Files changed (163) hide show
  1. package/README.md +126 -14
  2. package/README.zh-CN.md +79 -4
  3. package/dist/args.d.ts +8 -0
  4. package/dist/args.d.ts.map +1 -1
  5. package/dist/args.js +167 -28
  6. package/dist/args.js.map +1 -1
  7. package/dist/args.test.d.ts +2 -0
  8. package/dist/args.test.d.ts.map +1 -0
  9. package/dist/args.test.js +125 -0
  10. package/dist/args.test.js.map +1 -0
  11. package/dist/child-process.d.ts +3 -0
  12. package/dist/child-process.d.ts.map +1 -1
  13. package/dist/child-process.js +26 -0
  14. package/dist/child-process.js.map +1 -1
  15. package/dist/child-process.test.js +13 -0
  16. package/dist/child-process.test.js.map +1 -1
  17. package/dist/cli-exit.test.d.ts +2 -0
  18. package/dist/cli-exit.test.d.ts.map +1 -0
  19. package/dist/cli-exit.test.js +84 -0
  20. package/dist/cli-exit.test.js.map +1 -0
  21. package/dist/cli.js +16 -2
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config.d.ts +1 -0
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +1 -0
  26. package/dist/config.js.map +1 -1
  27. package/dist/extensions/review.d.ts +2 -0
  28. package/dist/extensions/review.js +22 -0
  29. package/dist/extensions/rv-prompts.d.ts +19 -0
  30. package/dist/extensions/rv-prompts.js +132 -0
  31. package/dist/index.d.ts +22 -7
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +15 -6
  34. package/dist/index.js.map +1 -1
  35. package/dist/json-events.d.ts +71 -0
  36. package/dist/json-events.d.ts.map +1 -1
  37. package/dist/json-events.js +250 -0
  38. package/dist/json-events.js.map +1 -1
  39. package/dist/json-events.test.js +217 -1
  40. package/dist/json-events.test.js.map +1 -1
  41. package/dist/loop.d.ts +36 -0
  42. package/dist/loop.d.ts.map +1 -0
  43. package/dist/loop.js +73 -0
  44. package/dist/loop.js.map +1 -0
  45. package/dist/loop.test.d.ts +2 -0
  46. package/dist/loop.test.d.ts.map +1 -0
  47. package/dist/loop.test.js +83 -0
  48. package/dist/loop.test.js.map +1 -0
  49. package/dist/matcher.d.ts +74 -0
  50. package/dist/matcher.d.ts.map +1 -0
  51. package/dist/matcher.js +202 -0
  52. package/dist/matcher.js.map +1 -0
  53. package/dist/matcher.test.d.ts +2 -0
  54. package/dist/matcher.test.d.ts.map +1 -0
  55. package/dist/matcher.test.js +161 -0
  56. package/dist/matcher.test.js.map +1 -0
  57. package/dist/meta-footer.d.ts +15 -1
  58. package/dist/meta-footer.d.ts.map +1 -1
  59. package/dist/meta-footer.js +108 -0
  60. package/dist/meta-footer.js.map +1 -1
  61. package/dist/meta-footer.test.js +43 -1
  62. package/dist/meta-footer.test.js.map +1 -1
  63. package/dist/panel-aggregate.d.ts +18 -0
  64. package/dist/panel-aggregate.d.ts.map +1 -0
  65. package/dist/panel-aggregate.js +247 -0
  66. package/dist/panel-aggregate.js.map +1 -0
  67. package/dist/panel-aggregate.test.d.ts +2 -0
  68. package/dist/panel-aggregate.test.d.ts.map +1 -0
  69. package/dist/panel-aggregate.test.js +461 -0
  70. package/dist/panel-aggregate.test.js.map +1 -0
  71. package/dist/panel-cancellation.test.d.ts +2 -0
  72. package/dist/panel-cancellation.test.d.ts.map +1 -0
  73. package/dist/panel-cancellation.test.js +24 -0
  74. package/dist/panel-cancellation.test.js.map +1 -0
  75. package/dist/panel-cli.test.d.ts +2 -0
  76. package/dist/panel-cli.test.d.ts.map +1 -0
  77. package/dist/panel-cli.test.js +239 -0
  78. package/dist/panel-cli.test.js.map +1 -0
  79. package/dist/panel-config.d.ts +19 -0
  80. package/dist/panel-config.d.ts.map +1 -0
  81. package/dist/panel-config.js +115 -0
  82. package/dist/panel-config.js.map +1 -0
  83. package/dist/panel-config.test.d.ts +2 -0
  84. package/dist/panel-config.test.d.ts.map +1 -0
  85. package/dist/panel-config.test.js +94 -0
  86. package/dist/panel-config.test.js.map +1 -0
  87. package/dist/panel-loop.test.d.ts +2 -0
  88. package/dist/panel-loop.test.d.ts.map +1 -0
  89. package/dist/panel-loop.test.js +118 -0
  90. package/dist/panel-loop.test.js.map +1 -0
  91. package/dist/panel-tools.test.d.ts +2 -0
  92. package/dist/panel-tools.test.d.ts.map +1 -0
  93. package/dist/panel-tools.test.js +12 -0
  94. package/dist/panel-tools.test.js.map +1 -0
  95. package/dist/panel-usage.d.ts +6 -0
  96. package/dist/panel-usage.d.ts.map +1 -0
  97. package/dist/panel-usage.js +21 -0
  98. package/dist/panel-usage.js.map +1 -0
  99. package/dist/panel-view.d.ts +41 -0
  100. package/dist/panel-view.d.ts.map +1 -0
  101. package/dist/panel-view.js +90 -0
  102. package/dist/panel-view.js.map +1 -0
  103. package/dist/panel-view.test.d.ts +2 -0
  104. package/dist/panel-view.test.d.ts.map +1 -0
  105. package/dist/panel-view.test.js +45 -0
  106. package/dist/panel-view.test.js.map +1 -0
  107. package/dist/panel.d.ts +20 -0
  108. package/dist/panel.d.ts.map +1 -0
  109. package/dist/panel.js +393 -0
  110. package/dist/panel.js.map +1 -0
  111. package/dist/presets.d.ts +2 -1
  112. package/dist/presets.d.ts.map +1 -1
  113. package/dist/presets.js +10 -0
  114. package/dist/presets.js.map +1 -1
  115. package/dist/prompt.d.ts +14 -1
  116. package/dist/prompt.d.ts.map +1 -1
  117. package/dist/prompt.js +61 -15
  118. package/dist/prompt.js.map +1 -1
  119. package/dist/prompt.test.d.ts +2 -0
  120. package/dist/prompt.test.d.ts.map +1 -0
  121. package/dist/prompt.test.js +37 -0
  122. package/dist/prompt.test.js.map +1 -0
  123. package/dist/review-events.d.ts +70 -0
  124. package/dist/review-events.d.ts.map +1 -0
  125. package/dist/review-events.js +52 -0
  126. package/dist/review-events.js.map +1 -0
  127. package/dist/review-events.test.d.ts +2 -0
  128. package/dist/review-events.test.d.ts.map +1 -0
  129. package/dist/review-events.test.js +47 -0
  130. package/dist/review-events.test.js.map +1 -0
  131. package/dist/review-result.d.ts +5 -0
  132. package/dist/review-result.d.ts.map +1 -0
  133. package/dist/review-result.js +98 -0
  134. package/dist/review-result.js.map +1 -0
  135. package/dist/review-result.test.d.ts +2 -0
  136. package/dist/review-result.test.d.ts.map +1 -0
  137. package/dist/review-result.test.js +196 -0
  138. package/dist/review-result.test.js.map +1 -0
  139. package/dist/review.d.ts +10 -4
  140. package/dist/review.d.ts.map +1 -1
  141. package/dist/review.js +82 -20
  142. package/dist/review.js.map +1 -1
  143. package/dist/review.meta.test.js +2 -2
  144. package/dist/review.meta.test.js.map +1 -1
  145. package/dist/skill-protocol.test.d.ts +2 -0
  146. package/dist/skill-protocol.test.d.ts.map +1 -0
  147. package/dist/skill-protocol.test.js +35 -0
  148. package/dist/skill-protocol.test.js.map +1 -0
  149. package/dist/types.d.ts +151 -5
  150. package/dist/types.d.ts.map +1 -1
  151. package/dist/types.js +13 -0
  152. package/dist/types.js.map +1 -1
  153. package/extensions/panel-tool.test.ts +30 -0
  154. package/extensions/panel-tool.ts +169 -0
  155. package/extensions/review.ts +3 -1
  156. package/extensions/rv-prompts.test.ts +8 -1
  157. package/extensions/rv-prompts.ts +6 -4
  158. package/package.json +14 -5
  159. package/resources/panel-presets.json +21 -0
  160. package/resources/review-presets.json +2 -2
  161. package/resources/system-prompt.md +11 -1
  162. package/skills/pi-review/SKILL.md +93 -15
  163. package/skills/pi-review/references/codex-tools.md +19 -6
@@ -0,0 +1,169 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { Writable } from "node:stream";
4
+ import { Type } from "typebox";
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+ import { Container, Markdown, Spacer, Text, type Component } from "@earendil-works/pi-tui";
7
+ import { createPanelViewState, reducePanelEvent, spawnStreamingChild, type PanelViewState, type ReviewEvent } from "@zephyrdeng/pi-review";
8
+
9
+ type Theme = { fg: (color: "toolTitle" | "muted" | "error" | "success" | "accent", text: string) => string; bold: (text: string) => string };
10
+ type PanelToolDetails = { state: PanelViewState; error?: string };
11
+
12
+ const extensionDir = path.dirname(fileURLToPath(import.meta.url));
13
+ const cliPath = path.resolve(extensionDir, "../bin/pi-review.js");
14
+
15
+ function duration(ms: number | undefined): string {
16
+ if (!ms) return "0s";
17
+ return `${Math.floor(ms / 1000)}s`;
18
+ }
19
+
20
+ function usageTokens(state: PanelViewState): string {
21
+ return state.aggregate.usage ? `${state.aggregate.usage.totalTokens.toLocaleString()} tok` : "—";
22
+ }
23
+
24
+ function statusSymbol(status: string): string {
25
+ return status === "completed" ? "✓" : status === "failed" || status === "cancelled" ? "✗" : status === "running" ? "●" : "○";
26
+ }
27
+
28
+ function compactText(state: PanelViewState, theme: Theme): string {
29
+ const elapsed = state.startedAt ? duration((state.completedAt ?? Date.now()) - state.startedAt) : "0s";
30
+ const progress = `${state.aggregate.completed}/${state.aggregate.total} completed`;
31
+ const header = theme.fg("toolTitle", theme.bold(`pi-review panel ${progress}`)) + theme.fg("muted", ` · ${state.phase} · ${elapsed} · ${usageTokens(state)}`);
32
+ const rows = Object.values(state.reviewers).map((reviewer) => {
33
+ const active = reviewer.activeTool ? ` · ${reviewer.activeTool}` : "";
34
+ const elapsed = reviewer.startedAt ? ` · ${duration((reviewer.completedAt ?? Date.now()) - reviewer.startedAt)}` : "";
35
+ const tokens = reviewer.usage ? ` · ${reviewer.usage.totalTokens.toLocaleString()} tok` : "";
36
+ return `${theme.fg(reviewer.status === "failed" ? "error" : reviewer.status === "completed" ? "success" : "accent", statusSymbol(reviewer.status))} ${theme.fg("toolTitle", reviewer.reviewerId)}${theme.fg("muted", ` ${reviewer.role}${active}${elapsed}${tokens}`)}`;
37
+ });
38
+ return [header, ...rows].join("\n");
39
+ }
40
+
41
+ function finalMarkdown(state: PanelViewState): string {
42
+ const meta = state.meta;
43
+ if (!meta) return "";
44
+ const confirmed = meta.confirmedClusters.map((finding) => `- ${finding.summary} (${finding.supportingReviewerIds.join(", ")})`);
45
+ const advisories = meta.advisories.map((finding) => `- ${finding.summary} (${finding.supportingReviewerIds.join(", ")})`);
46
+ return [
47
+ "### Panel result",
48
+ `Health: ${meta.panelHealth}; status: ${meta.status}.`,
49
+ confirmed.length ? `### Confirmed findings\n${confirmed.join("\n")}` : "### Confirmed findings\nNone.",
50
+ advisories.length ? `### Advisories\n${advisories.join("\n")}` : "",
51
+ ].filter(Boolean).join("\n\n");
52
+ }
53
+
54
+ export function renderPanelResult(details: PanelToolDetails | undefined, expanded: boolean, theme: Theme, previous?: Component): Component {
55
+ if (!details) return new Text(theme.fg("muted", "Panel review has no progress details."), 0, 0);
56
+ if (!expanded) {
57
+ const text = previous instanceof Text ? previous : new Text("", 0, 0);
58
+ text.setText(compactText(details.state, theme));
59
+ return text;
60
+ }
61
+ const container = new Container();
62
+ container.addChild(new Text(compactText(details.state, theme), 0, 0));
63
+ for (const reviewer of Object.values(details.state.reviewers)) {
64
+ if (reviewer.recentActivity.length === 0) continue;
65
+ container.addChild(new Spacer(1));
66
+ container.addChild(new Text(theme.fg("muted", `${reviewer.reviewerId} activity`), 0, 0));
67
+ container.addChild(new Text(reviewer.recentActivity.join("\n"), 0, 0));
68
+ }
69
+ const markdown = finalMarkdown(details.state);
70
+ if (markdown) {
71
+ container.addChild(new Spacer(1));
72
+ // Pi supplies a Markdown theme to built-in renderers; plain text keeps this
73
+ // extension portable across public API versions while still using Markdown.
74
+ container.addChild(new Markdown(markdown, 0, 0, {
75
+ heading: theme.bold,
76
+ link: (text) => text,
77
+ linkUrl: (text) => text,
78
+ code: (text) => text,
79
+ codeBlock: (text) => text,
80
+ codeBlockBorder: (text) => text,
81
+ quote: (text) => text,
82
+ quoteBorder: (text) => text,
83
+ hr: (text) => text,
84
+ listBullet: (text) => text,
85
+ bold: theme.bold,
86
+ italic: (text) => text,
87
+ strikethrough: (text) => text,
88
+ underline: (text) => text,
89
+ }));
90
+ }
91
+ if (details.error) {
92
+ container.addChild(new Spacer(1));
93
+ container.addChild(new Text(theme.fg("error", details.error), 0, 0));
94
+ }
95
+ return container;
96
+ }
97
+
98
+ export function registerPanelReviewTool(pi: ExtensionAPI): void {
99
+ pi.registerTool({
100
+ name: "pi_review",
101
+ label: "Pi Review Panel",
102
+ description: "Run an isolated read-only pi-review panel and render each reviewer live.",
103
+ renderShell: "self",
104
+ parameters: Type.Object({
105
+ target: Type.String({ description: "Review target, such as @src or a code-review brief." }),
106
+ mode: Type.Optional(Type.String({ description: "Review mode; defaults to code." })),
107
+ panel: Type.Optional(Type.String({ description: "Named panel preset; defaults to code-experts." })),
108
+ model: Type.Optional(Type.String({ description: "Optional reviewer model." })),
109
+ thinking: Type.Optional(Type.String({ description: "Optional thinking level." })),
110
+ }),
111
+ async execute(_toolCallId, params, signal, onUpdate) {
112
+ let state = createPanelViewState();
113
+ let buffer = "";
114
+ let stderr = "";
115
+ const publish = () => onUpdate?.({ content: [{ type: "text", text: `Panel: ${state.aggregate.completed}/${state.aggregate.total} completed` }], details: { state } });
116
+ const consume = (chunk: string) => {
117
+ buffer += chunk;
118
+ let newline: number;
119
+ while ((newline = buffer.indexOf("\n")) >= 0) {
120
+ const line = buffer.slice(0, newline);
121
+ buffer = buffer.slice(newline + 1);
122
+ try {
123
+ state = reducePanelEvent(state, JSON.parse(line) as ReviewEvent);
124
+ publish();
125
+ } catch {
126
+ // Only the stable pi-review event protocol reaches this stream.
127
+ }
128
+ }
129
+ };
130
+ const stdoutSink = new Writable({ write(chunk, _encoding, callback) { consume(String(chunk)); callback(); } });
131
+ const stderrSink = new Writable({
132
+ write(chunk, _encoding, callback) {
133
+ const next = stderr + String(chunk);
134
+ stderr = next.length > 2000 ? next.slice(-2000) : next;
135
+ callback();
136
+ },
137
+ });
138
+ const args = [cliPath, "--panel", params.panel ?? "code-experts", "--mode", params.mode ?? "code", "--output-format", "events-jsonl"];
139
+ if (params.model) args.push("--model", params.model);
140
+ if (params.thinking) args.push("--thinking", params.thinking);
141
+ args.push("--", params.target);
142
+ const child = await spawnStreamingChild(process.execPath, args, { cwd: process.cwd(), env: process.env, stdoutSink, stderrSink, signal, processGroup: true });
143
+ if (buffer.trim()) {
144
+ const tail = buffer;
145
+ buffer = "";
146
+ consume(`${tail}\n`);
147
+ }
148
+ const error = child.error
149
+ ? child.error.message
150
+ : state.meta
151
+ ? undefined
152
+ : [`pi-review ended before a final event (${child.status ?? child.signal ?? "unknown"})`, stderr.trim()].filter(Boolean).join(": ");
153
+ const details: PanelToolDetails = { state, ...(error ? { error } : {}) };
154
+ return {
155
+ content: [{ type: "text", text: error ?? `Panel completed: ${state.meta?.status ?? "unknown"}` }],
156
+ details,
157
+ ...(error ? { isError: true } : {}),
158
+ };
159
+ },
160
+ renderCall(args, theme, context) {
161
+ const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
162
+ text.setText(theme.fg("toolTitle", theme.bold("pi_review ")) + theme.fg("muted", `${args.panel ?? "code-experts"} · ${args.mode ?? "code"} · ${args.target}`));
163
+ return text;
164
+ },
165
+ renderResult(result, { expanded }, theme, context) {
166
+ return renderPanelResult(result.details as PanelToolDetails | undefined, expanded, theme, context.lastComponent);
167
+ },
168
+ });
169
+ }
@@ -10,6 +10,7 @@ import {
10
10
  RV_COMPLETIONS,
11
11
  validateRvParsed,
12
12
  } from "./rv-prompts.js";
13
+ import { registerPanelReviewTool } from "./panel-tool.js";
13
14
 
14
15
  /**
15
16
  * Captured at `session_start` so the synchronous-ish `getArgumentCompletions`
@@ -68,6 +69,7 @@ function toModelInfo(m: {
68
69
  }
69
70
 
70
71
  export default function piReviewExtension(pi: ExtensionAPI) {
72
+ registerPanelReviewTool(pi);
71
73
  pi.on("session_start", (_event, ctx) => {
72
74
  try {
73
75
  const available = ctx.modelRegistry?.getAvailable?.() ?? [];
@@ -130,4 +132,4 @@ export default function piReviewExtension(pi: ExtensionAPI) {
130
132
  pi.sendUserMessage(buildRvOrchestrationPrompt(parsed, localeForHandler(ctx)));
131
133
  },
132
134
  });
133
- }
135
+ }
@@ -70,10 +70,17 @@ describe("parseRvArgs / validateRvParsed", () => {
70
70
  const p = parseRvArgs("@src/foo.ts");
71
71
  const prompt = buildRvOrchestrationPrompt(p);
72
72
  assert.match(prompt, /Do NOT add --no-stream or --progress-log/);
73
+ assert.match(prompt, /rendered panel result and status/);
73
74
  assert.match(prompt, /ASCII pi-review footer/);
74
75
  assert.doesNotMatch(prompt, /PI_REVIEW_META:/);
75
76
  });
76
77
 
78
+ it("continuations retain the session-aware CLI path", () => {
79
+ const prompt = buildRvOrchestrationPrompt(parseRvArgs("--continue /tmp/review.jsonl @src/foo.ts"));
80
+ assert.match(prompt, /Execute:\npi-review --continue \/tmp\/review\.jsonl -- @src\/foo\.ts/);
81
+ assert.doesNotMatch(prompt, /Call pi_review with target/);
82
+ });
83
+
77
84
  it("buildPiReviewArgv matches for initial and continue", () => {
78
85
  const initial = parseRvArgs("--mode plan --model m/a @doc.md");
79
86
  assert.deepEqual(buildPiReviewArgv(initial, "@doc.md"), [
@@ -99,4 +106,4 @@ describe("parseRvArgs / validateRvParsed", () => {
99
106
  "more",
100
107
  ]);
101
108
  });
102
- });
109
+ });
@@ -144,10 +144,10 @@ function piHostRules(): string {
144
144
  return [
145
145
  "Follow the pi-review skill.",
146
146
  "Host: Pi interactive session (/rv).",
147
- "Run pi-review in an isolated child session; return the review conclusion (Markdown sections) plus the ASCII pi-review footer.",
147
+ "For new review runs, call the pi_review custom tool. It launches pi-review in isolated reviewer sessions and renders panel progress live.",
148
148
  "CLI defaults: stream child output live. Do NOT add --no-stream or --progress-log unless the user explicitly asked.",
149
149
  "Do not edit, patch, commit, or implement findings unless the user asks separately.",
150
- "After pi-review exits, show the user the ASCII footer (lines starting with ── pi-review). Do not paste PI_REVIEW_META_JSON to the user; use Session from the footer for /rv --continue.",
150
+ "For pi_review panel runs, report the tool's rendered panel result and status. For CLI fallback and --continue runs, show the ASCII pi-review footer (lines starting with ── pi-review). Do not paste PI_REVIEW_META_JSON to the user; use Session from a CLI footer for /rv --continue.",
151
151
  ].join(" ");
152
152
  }
153
153
 
@@ -216,7 +216,9 @@ export function buildRvOrchestrationPrompt(parsed: RvParsed, locale: RvLocale =
216
216
  `Mode: ${parsed.mode}. Follow the preset named in review-presets.json when present.`,
217
217
  modelStep,
218
218
  continueNote,
219
- `Execute:\n${cliLine}`,
219
+ parsed.continueHandle || parsed.keepSession || parsed.noStream
220
+ ? `Execute:\n${cliLine}`
221
+ : `Call pi_review with target=${JSON.stringify(target)}, mode=${JSON.stringify(parsed.mode)}, and any selected model/thinking values. The tool uses the code-experts panel by default.`,
220
222
  ]
221
223
  .filter(Boolean)
222
224
  .join("\n\n");
@@ -233,4 +235,4 @@ export const RV_COMPLETIONS: { value: string; hint: string }[] = [
233
235
  { value: "--keep-session", hint: "persist session for follow-up" },
234
236
  { value: "--no-stream", hint: "rare: buffer until exit (not default in Pi)" },
235
237
  { value: "--continue ", hint: "resume session; same optional flags as initial /rv" },
236
- ];
238
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zephyrdeng/pi-review",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
4
4
  "description": "Isolated AI-powered code and plan reviews from the command line",
5
5
  "type": "module",
6
6
  "author": "ZephyrDeng <zephyrTang@aliyun.com>",
@@ -24,7 +24,7 @@
24
24
  "cli"
25
25
  ],
26
26
  "bin": {
27
- "pi-review": "./bin/pi-review.js"
27
+ "pi-review": "bin/pi-review.js"
28
28
  },
29
29
  "exports": {
30
30
  ".": {
@@ -50,14 +50,20 @@
50
50
  ]
51
51
  },
52
52
  "peerDependencies": {
53
- "@earendil-works/pi-coding-agent": "*"
53
+ "@earendil-works/pi-coding-agent": "*",
54
+ "@earendil-works/pi-tui": "*"
54
55
  },
55
56
  "peerDependenciesMeta": {
56
57
  "@earendil-works/pi-coding-agent": {
57
58
  "optional": true
59
+ },
60
+ "@earendil-works/pi-tui": {
61
+ "optional": true
58
62
  }
59
63
  },
60
64
  "devDependencies": {
65
+ "@earendil-works/pi-coding-agent": "^0.80.7",
66
+ "@earendil-works/pi-tui": "^0.80.7",
61
67
  "@types/node": "^22.0.0",
62
68
  "husky": "^9.1.7",
63
69
  "typescript": "^5.8.0"
@@ -65,14 +71,17 @@
65
71
  "scripts": {
66
72
  "prepare": "husky",
67
73
  "build": "tsc",
68
- "check": "tsc --noEmit",
74
+ "check": "tsc --noEmit && tsc --noEmit -p tsconfig.extension.json",
69
75
  "smoke": "node dist/cli.js --help >/dev/null",
70
- "test": "npx --yes tsx --test src/child-process.test.ts src/review.meta.test.ts src/meta-footer.test.ts src/json-events.test.ts src/install-args.test.ts extensions/rv-prompts.test.ts extensions/rv-completions.test.ts extensions/rv-model-priorities.test.ts extensions/rv-locale.test.ts extensions/rv-semantic.test.ts",
76
+ "test": "npm run build && npx --yes tsx --test src/args.test.ts src/cli-exit.test.ts src/child-process.test.ts src/review.meta.test.ts src/review-result.test.ts src/loop.test.ts src/meta-footer.test.ts src/prompt.test.ts src/skill-protocol.test.ts src/json-events.test.ts src/install-args.test.ts src/panel-aggregate.test.ts src/matcher.test.ts src/panel-config.test.ts src/panel-loop.test.ts src/panel-cli.test.ts src/review-events.test.ts src/panel-view.test.ts src/panel-cancellation.test.ts src/panel-tools.test.ts extensions/rv-prompts.test.ts extensions/rv-completions.test.ts extensions/rv-model-priorities.test.ts extensions/rv-locale.test.ts extensions/rv-semantic.test.ts extensions/panel-tool.test.ts",
71
77
  "test:rv": "npx --yes tsx --test extensions/rv-prompts.test.ts extensions/rv-completions.test.ts extensions/rv-model-priorities.test.ts extensions/rv-locale.test.ts extensions/rv-semantic.test.ts",
72
78
  "prepublishOnly": "npm run build",
73
79
  "pack:dry": "npm pack --dry-run"
74
80
  },
75
81
  "engines": {
76
82
  "node": ">=20"
83
+ },
84
+ "dependencies": {
85
+ "typebox": "^1.1.38"
77
86
  }
78
87
  }
@@ -0,0 +1,21 @@
1
+ {
2
+ "code-experts": {
3
+ "description": "Code review panel with independent correctness, security, and testing lenses.",
4
+ "consensus": "quorum",
5
+ "minAgree": 2,
6
+ "reviewers": [
7
+ {
8
+ "id": "correctness",
9
+ "role": "Correctness reviewer: focus on logic errors, regressions, concurrency, API contracts, edge cases, and maintainability."
10
+ },
11
+ {
12
+ "id": "security",
13
+ "role": "Security reviewer: focus on input validation, authn/authz, secrets, injection, unsafe deserialization, and trust boundaries."
14
+ },
15
+ {
16
+ "id": "testing",
17
+ "role": "Testing reviewer: focus on missing tests, weak assertions, flakiness, coverage gaps, and behavior that would escape the test suite."
18
+ }
19
+ ]
20
+ }
21
+ }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "code": {
3
3
  "description": "Code review only: inspect provided files, diffs, or paths and return review conclusions without editing.",
4
- "tools": ["read", "grep", "find", "ls", "bash"],
4
+ "tools": ["read", "grep", "find", "ls"],
5
5
  "thinking": "high",
6
- "instructions": "Review code or code changes only. Do not edit, write, patch, format, commit, or otherwise mutate files. Use bash only for read-only inspection commands such as git diff, git status, git show, tests with explicit user permission, or listing files. Focus on correctness, regressions, security, concurrency, API contracts, edge cases, maintainability, and missing tests."
6
+ "instructions": "Review code or code changes only. Do not edit, write, patch, format, commit, or otherwise mutate files. Use the enabled read-only tools for inspection. Focus on correctness, regressions, security, concurrency, API contracts, edge cases, maintainability, and missing tests."
7
7
  },
8
8
  "plan": {
9
9
  "description": "High-level plan review with multiple expert lenses; output a decisive conclusion.",
@@ -34,7 +34,17 @@ One of: approve | request_changes | needs_clarification | blocked
34
34
  Short conclusion in 2-5 bullets.
35
35
 
36
36
  ## Findings
37
- Prioritized findings. Each finding should include severity, evidence, impact, and recommendation. If there are no material findings, write "No material findings."
37
+ Prioritized findings. For every material finding, use this exact shape (increment IDs as F1, F2, ...):
38
+
39
+ ### F1: <summary>
40
+ - Severity: critical | high | medium | low
41
+ - Path: <path or none>
42
+ - Actionable: yes | no
43
+ - Evidence: <concrete evidence>
44
+ - Impact: <why it matters>
45
+ - Recommendation: <specific next step>
46
+
47
+ Set Actionable to yes when the host must fix or consciously reject the finding before clean closeout. Use no for informational or explicitly out-of-scope follow-up findings. If there are no material findings, write "No material findings."
38
48
 
39
49
  ## Risks and Blind Spots
40
50
  What could still be wrong, missing, or unverified.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pi-review
3
- description: Use pi-review to delegate isolated code, diff, repository, architecture, or plan reviews to a fresh Pi session and return only the review conclusion. Model selection presets for code, frontend, and plan review (see references/model-selection.md). On Claude Code or Codex, default to --progress-log with a background shell run and tail the log (chat tools buffer stdout). Use when the user asks for Pi review, isolated review, code review, plan review, challenge review, review status/progress, or wants to avoid context pollution.
3
+ description: Use pi-review to delegate isolated code, diff, repository, architecture, or plan reviews to fresh Pi sessions and return review conclusions. Also use for loop review, review-fix-re-review closeout, structured review gates, and review status/progress. Model selection presets for code, frontend, and plan review are in references/model-selection.md. On Claude Code or Codex, default to --progress-log with a background shell run and tail the log (chat tools buffer stdout).
4
4
  ---
5
5
 
6
6
  # Pi Review
@@ -34,26 +34,21 @@ When resolving this fallback, use the actual directory that contains this `SKILL
34
34
 
35
35
  | Host | How to run `pi-review` |
36
36
  |------|-------------------------|
37
- | **Pi** (`/rv`) | Foreground CLI, **default streaming** only. Do **not** add `--no-stream` or `--progress-log` unless the user explicitly asks. |
37
+ | **Pi** (`/rv`) | New reviews call the native `pi_review` tool, which launches the packaged CLI in event mode and renders live Panel state. Continuations, kept sessions, and explicit `--no-stream` retain the foreground CLI path. |
38
38
  | **Claude Code, Codex, Cursor**, and similar AI agents | **Default: `--progress-log <path>` + background run + tail** (see below). Do **not** rely on foreground Bash stdout for “streaming” — the tool buffers until exit. |
39
39
  | Scripts / CI | Foreground is fine; use `--no-stream` only when you must buffer until exit. |
40
40
 
41
41
  ## Pi host (`/rv`)
42
42
 
43
- - In **Pi interactive** sessions, run `pi-review` with **default streaming only** — do **not** add `--no-stream` or `--progress-log` unless the user explicitly asks.
44
- - The `/rv` extension injects these rules into the parent agent; ordinary `/rv @path` needs no extra flags.
43
+ - In **Pi interactive** sessions, ordinary `/rv @path` calls the native `pi_review` tool and needs no streaming flags. The tool owns normalized events, live rendering, and the packaged CLI process.
44
+ - `--continue`, `--keep-session`, and explicit `--no-stream` use the shell CLI path because they retain single-review session semantics.
45
45
 
46
46
  ## Streaming vs agent hosts (Claude Code / Codex)
47
47
 
48
- - `pi-review` **streams** child `pi` stdout/stderr to the process terminal by default (`--no-stream` buffers until exit). That helps **Pi** and real terminals, not most agent chat UIs.
49
- - **Claude Code, Cursor, Codex**, and similar hosts **buffer tool stdout** until the bash command exits, so the chat shows the review **all at once** even though the CLI is streaming. That is expected—not a misconfiguration.
50
- - Default `text` mode also produces **no stdout** during tool-use/thinking (only the final answer), so even a local terminal stays quiet for most of a multi-minute review.
51
- - **Recommended for Claude Code / Codex (live progress):** always use `--progress-log <path>` unless the user explicitly wants a silent blocking wait:
52
- 1. Pick a writable path (e.g. `/tmp/pi-review-<id>.jsonl`).
53
- 2. Start `pi-review --progress-log <path> ...` in a **background** or **async** shell invocation (not a blocking foreground Bash that you wait on with no updates).
54
- 3. While the child runs, **tail** the log and summarize milestones to the user (filter out `message_update` / `tool_execution_update` unless they asked for token-level detail).
55
- 4. When the process exits, show the Markdown body and ASCII `── pi-review` footer from the command result (or re-read the final output as the skill’s step 4 describes).
56
- - Fall back to a **local terminal** with default streaming only if the host cannot run background commands or tail a file.
48
+ - `pi-review` always runs the child in `--mode json` internally. In streaming mode it forwards **readable text deltas** to stdout live and writes **semantic milestone notices** (`pi-review: review started`, `pi-review: tool <name> started/finished`, `pi-review: review finished`) to **stderr**. Token usage (`input`/`output`/`cache`/`reasoning`) is accumulated by default — no `--progress-log` required to see it in the ASCII footer or `PI_REVIEW_META_JSON`.
49
+ - `--progress-log <path>` is now an **optional** convenience: it tees the raw `--mode json` event stream to a file for external observation (tail, debugging). It no longer gates token visibility.
50
+ - **Claude Code, Cursor, Codex** and similar hosts still buffer a Bash tool's stdout until the command exits. The semantic milestone notices on stderr + the final Markdown + ASCII footer on stdout give you progress and the result without needing to tail a file. If you still want the full event log for fine-grained debugging, add `--progress-log <path>` and tail it.
51
+ - **Pi interactive** sessions stream text deltas live to the terminal by default — no `--no-stream` or `--progress-log` needed.
57
52
 
58
53
  ## Review status (no slash command)
59
54
 
@@ -78,6 +73,87 @@ There is **no** `/status` slash in this skill. When the user asks for **review s
78
73
 
79
74
  Do not implement findings from a status check; status is informational only.
80
75
 
76
+ ## Loop closeout protocol
77
+
78
+ Use this protocol when the host is closing out implementation work and may edit between reviews. `pi-review` and every child session remain review-only; the host agent owns all fixes.
79
+
80
+ 1. **Freeze the scope baseline before the first review.** Record the originating task, accepted files/modules, required behavior, and proof commands. Do not silently expand this baseline because a reviewer noticed adjacent cleanup.
81
+ 2. **Run a bounded structured gate.** Prefer the productized command:
82
+ ```bash
83
+ pi-review loop --max-rounds 1 [shared review options] -- <@files|text...>
84
+ ```
85
+ A one-round invocation gives the host a fix point between reviews. Increase `--max-rounds` only when repeated independent review of the unchanged tree is intentional; the CLI never waits for edits. Re-invocation after a host fix is a new process. On Claude Code / Codex, compose `loop` with the normal `--progress-log <path>` background + tail workflow. On Pi, retain default foreground streaming. `loop` rejects `--keep-session`, `--continue`, and `--name`.
86
+ 3. **Read the gate signals.** Parse `status`, `findings`, and `actionableCount` from each `PI_REVIEW_META_JSON` line and use the final loop summary. Expected statuses are `clean`, `has_findings`, `needs_human`, and `blocked`.
87
+ 4. **Apply the scope governor to every finding:**
88
+ - **in-scope blocker** — accepted, actionable, and required by the frozen task; the host may fix it now.
89
+ - **follow-up** — valid but outside the baseline or not required for safe closeout; record it without drive-by edits.
90
+ - **stop-and-escalate** — ambiguous intent, architectural expansion, unsafe migration, blocked tooling, or anything requiring a human decision; stop rather than guess.
91
+ - A rejected finding must be intentional and recorded with its rationale in the final report. Never silently ignore it.
92
+ - Check sibling instances of the same bug class only inside the frozen task/PR scope. Do not turn the check into a repository-wide refactor.
93
+ 5. **Fix only in-scope blockers in the host.** Never ask the child review session to implement fixes. After each host patch, rerun the narrowest relevant proof (focused test, typecheck, lint, or reproduction) before re-reviewing.
94
+ 6. **Re-review until a stop condition:**
95
+ - `clean` → gate open; proceed to final proof and closeout.
96
+ - `has_findings` → classify, fix accepted in-scope blockers, and re-invoke within the agreed host-cycle budget.
97
+ - `needs_human` or `blocked` → stop early and escalate with the review history.
98
+ - budget exhausted → report remaining findings and ask for a decision; do not loop indefinitely.
99
+ 7. **Detect non-convergence.** After two non-converging patch cycles (the same finding persists, findings oscillate, or scope grows), pause and reclassify all remaining findings before any further edit.
100
+ 8. **Gate completion claims.** Never claim done, ship, commit-ready, or clean without a fresh `clean` result, unless the user gives explicit human acceptance of named remaining findings. Report accepted fixes, rejected findings with rationale, follow-ups, stop reason, and proof evidence.
101
+
102
+ The shell exit policy is: `0` clean, `1` status is `has_findings`, `2` usage error, `3` needs human, `4` blocked/runtime failure. A non-zero loop result is a gate signal, not permission for the child reviewer to edit.
103
+
104
+ ## Panel review
105
+
106
+ Panel review runs multiple **independent** reviewers in isolated child sessions and aggregates their findings into one gate result. Reviewers cannot see one another's findings, so agreement represents independent discovery.
107
+
108
+ ### When to use panel vs single review
109
+
110
+ - **Single review (default):** keep for small, low-cost changes. Reviewer count defaults to one; the existing execution, output, status mapping, and exit policy stay unchanged.
111
+ - **Panel review:** use when a change deserves independent scrutiny. Activate it with `--reviewers <n>` (2-8) or `--panel <name>`.
112
+
113
+ ### Consensus
114
+
115
+ A finding becomes a **confirmed finding** (gate-relevant) only when enough independent reviewers mark the issue **actionable**. Otherwise it stays a non-blocking **advisory**.
116
+
117
+ Consensus policies (default `quorum` for multi-reviewer panels):
118
+
119
+ | Policy | Threshold |
120
+ |--------|----------|
121
+ | `any` | one actionable reviewer confirms |
122
+ | `quorum` | configured minimum agreement (default 2; set with `--min-agree`) |
123
+ | `majority` | `floor(reviewers / 2) + 1` |
124
+ | `unanimous` | every reviewer |
125
+
126
+ Multi-reviewer panels default to **quorum with minimum agreement 2** so panel mode never silently becomes any-finding fail-closed. Single-review stays threshold one.
127
+
128
+ ### Advisories
129
+
130
+ Singleton (uncorroborated) findings remain visible as **advisories** but do **not** change clean status or fail the gate. They are recorded as non-blocking follow-ups; the host promotes them only with explicit human acceptance. Confirmed actionable clusters produce `has_findings`; no confirmed clusters produce `clean`.
131
+
132
+ ### Aggregation
133
+
134
+ Two-phase matching: deterministic matching on stable anchors (path + normalized summary) first; only ambiguous same-path candidates go to a constrained **semantic adjudicator** (enabled with `--consensus-model`). The adjudicator clusters findings and may **not** invent findings, drop findings, add evidence, or act as another reviewer — it has no write tools. Low-confidence matches stay separate advisories so uncertain similarity cannot manufacture quorum.
135
+
136
+ ### Cost multipliers
137
+
138
+ Cost is visible before execution. Reviewer runs = `--reviewers <n>` × `--max-rounds` (loop). One consensus-adjudication call may run per round when `--consensus-model` is set, tracked separately. Use `--concurrency <n>` to bound provider/machine pressure (default: reviewer count, never exceeds it).
139
+
140
+ ### Host-only fixes
141
+
142
+ Panel review composes with Loop Review: one loop round evaluates one **complete panel** with fresh reviewer sessions. The CLI never edits, patches, or waits for filesystem changes — the host agent remains the only actor allowed to fix between process invocations. Apply the scope governor to **confirmed** findings exactly as for single-review findings. Rejected confirmed findings must be recorded with rationale. `--max-rounds 1` keeps the patch-by-patch closeout workflow.
143
+
144
+ Panel review rejects `--keep-session`, `--continue`, and `--name` in v1 (reviewers run `--no-session`). Reviewer runtime failure → `blocked`; unstructured dirty output or unresolved clarification → `needs_human`; never silently clean.
145
+
146
+ ### Examples
147
+
148
+ ```bash
149
+ # Single panel review
150
+ pi-review --reviewers 3 --consensus quorum --min-agree 2 -- @src
151
+ # Expert preset
152
+ pi-review --panel code-experts --consensus majority -- @src
153
+ # Panel loop review (up to 6 reviewer runs + adjudication)
154
+ pi-review loop --reviewers 3 --consensus quorum --max-rounds 2 -- @src
155
+ ```
156
+
81
157
  ## Steps
82
158
 
83
159
  1. Prime the model catalog and pick a model:
@@ -116,8 +192,8 @@ Do not implement findings from a status check; status is informational only.
116
192
 
117
193
  4. Return the result:
118
194
  - Show the Markdown review body and the **ASCII footer** (`── pi-review` block on stdout). Do not paste raw `PI_REVIEW_META_JSON` to the user unless they ask for machine output.
119
- - Scripts: parse `PI_REVIEW_META_JSON:` from **stderr** (or set `PI_REVIEW_META_STDOUT=1` to also emit JSON on stdout).
120
- - Do not apply findings automatically; implementation is a separate user decision.
195
+ - Scripts: parse `PI_REVIEW_META_JSON:` from **stderr** (or set `PI_REVIEW_META_STDOUT=1` to emit JSON on stdout instead).
196
+ - For an ordinary review-only request, do not apply findings automatically; implementation is a separate user decision. When the user already requested implementation closeout, follow **Loop closeout protocol** and let only the host fix accepted in-scope blockers.
121
197
  Completion criterion: the user sees the review conclusion and readable footer.
122
198
 
123
199
  ## Examples
@@ -127,6 +203,8 @@ pi-review -- @src/foo.ts
127
203
  pi-review --model openai/gpt-5.5 -- @src/foo.ts
128
204
  pi-review --mode challenge --keep-session -- @docs/design.md
129
205
  pi-review --mode challenge --continue <sessionHandle> -- "expand finding 2"
206
+ pi-review loop --max-rounds 1 -- @src
207
+ pi-review loop --max-rounds 1 --progress-log /tmp/pi-review-loop.jsonl -- @src
130
208
  ```
131
209
 
132
210
  ## Status examples (user phrasing)
@@ -12,19 +12,32 @@ General Codex subagent and worktree mappings live in the **using-superpowers** s
12
12
 
13
13
  ## Long-running `pi-review` (default on Codex / Claude Code)
14
14
 
15
- Native shell tools usually **buffer stdout/stderr until the command exits**. A multi-minute `pi-review` run can look like a silent wait, then one large dump—even though the CLI streams by default. Default `text` mode also emits **no stdout** during tool-use/thinking (only the final answer).
15
+ Native shell tools usually **buffer stdout/stderr until the command exits**. A multi-minute `pi-review` run can look like a silent wait, then one large dump. `pi-review` mitigates this in two ways that need **no** `--progress-log`:
16
+
17
+ 1. **Semantic milestone notices on stderr** (`pi-review: review started`, `pi-review: tool <name> started/finished`, `pi-review: review finished`). Capture stderr separately or interleave it to show progress.
18
+ 2. **Token usage by default**: the ASCII footer and `PI_REVIEW_META_JSON` always include `thinking` + token usage (`input`/`output`/`cache`/`reasoning`) when the child session reports them — no `--progress-log` required.
16
19
 
17
20
  When the **pi-review** skill applies on Codex, Claude Code, or Cursor:
18
21
 
19
- 1. **Default:** `pi-review --progress-log <path> ...` (writable file, e.g. `/tmp/pi-review-<id>.jsonl`).
20
- 2. Start that command in a **background** or **async** shell invocation—not a single blocking call with no interim updates.
21
- 3. **Tail** the log while the child runs; summarize milestones to the user (skip `message_update` / `tool_execution_update` unless they want token-level detail).
22
- 4. After exit, show the Markdown review and ASCII `── pi-review` footer (skill step 4).
22
+ 1. **Default:** run `pi-review ... -- <target>` (foreground or background). Stderr carries milestone notices; stdout carries the review Markdown + ASCII footer on exit.
23
+ 2. If you want the **full** `--mode json` event log for fine-grained debugging, add `--progress-log <path>` and tail it — but this is optional, not required for tokens or progress.
24
+ 3. After exit, show the Markdown review body and ASCII `── pi-review` footer (skill step 4).
23
25
 
24
- **Pi interactive (`/rv`)** is different: foreground `pi-review`, default streaming only—do **not** auto-add `--progress-log` unless the user asked.
26
+ **Pi interactive (`/rv`)**: foreground `pi-review`, default streaming. Text deltas appear live on the terminal; no `--progress-log` unless the user asked.
25
27
 
26
28
  See the parent skill: sections *Default workflow by host* and *Streaming vs agent hosts (Claude Code / Codex)*.
27
29
 
30
+ ### Loop closeout
31
+
32
+ `loop` composes with the same default streaming + stderr-milestone workflow:
33
+
34
+ ```bash
35
+ pi-review loop --max-rounds 1 -- @src
36
+ # optional: --progress-log /tmp/pi-review-loop.jsonl for the full event log
37
+ ```
38
+
39
+ A non-zero command exit is an expected gate result when findings remain. Wait for the process, read the final `PI_REVIEW_META_JSON` / loop summary, let the host fix only accepted in-scope findings, rerun focused proof, and invoke a new loop process. The child review session never edits. Reuse or truncate the progress log deliberately because every loop round appends events to the chosen path.
40
+
28
41
  **Model choice:** after `pi-review models`, use **[model-selection.md](./model-selection.md)** (code / frontend / plan presets)—same guidance as Pi `/rv` completions.
29
42
 
30
43
  Example tail (milestone events only):