@zephyrdeng/pi-review 0.4.6 → 0.4.8
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 +22 -0
- package/README.zh-CN.md +1 -1
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +17 -4
- package/dist/args.js.map +1 -1
- package/dist/child-process.d.ts +20 -0
- package/dist/child-process.d.ts.map +1 -0
- package/dist/child-process.js +73 -0
- package/dist/child-process.js.map +1 -0
- package/dist/child-process.test.d.ts +2 -0
- package/dist/child-process.test.d.ts.map +1 -0
- package/dist/child-process.test.js +55 -0
- package/dist/child-process.test.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/json-events.d.ts +7 -0
- package/dist/json-events.d.ts.map +1 -0
- package/dist/json-events.js +62 -0
- package/dist/json-events.js.map +1 -0
- package/dist/json-events.test.d.ts +2 -0
- package/dist/json-events.test.d.ts.map +1 -0
- package/dist/json-events.test.js +88 -0
- package/dist/json-events.test.js.map +1 -0
- package/dist/review.d.ts +7 -1
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +85 -12
- package/dist/review.js.map +1 -1
- package/dist/review.meta.test.d.ts +2 -0
- package/dist/review.meta.test.d.ts.map +1 -0
- package/dist/review.meta.test.js +21 -0
- package/dist/review.meta.test.js.map +1 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/extensions/review.ts +1 -1
- package/extensions/rv-prompts.test.ts +12 -0
- package/extensions/rv-prompts.ts +11 -1
- package/package.json +2 -1
- package/skills/pi-review/SKILL.md +39 -2
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@ Works as a standalone CLI, a Pi package (extension + skill), or integrated into
|
|
|
20
20
|
- **Multiple review modes** — code review, multi-lens plan review, adversarial challenge review
|
|
21
21
|
- **Structured output** — machine-readable `PI_REVIEW_META` JSON footer for automation
|
|
22
22
|
- **Model-agnostic** — use any model available in your Pi installation
|
|
23
|
+
- **Live streaming** — child review output is forwarded as it arrives (use `--no-stream` to buffer)
|
|
24
|
+
- **Progress logging for AI hosts** — `--progress-log <path>` writes a live JSON event log so agent hosts that buffer tool stdout (Claude Code, Codex, ...) can still show real-time progress
|
|
23
25
|
- **Session continuity** — keep sessions alive for follow-up questions with `--keep-session`
|
|
24
26
|
- **Customizable presets** — extend or override review modes via JSON configuration
|
|
25
27
|
- **Pi package integration** — `/rv` slash command and agent skill included
|
|
@@ -134,6 +136,24 @@ pi-review --mode challenge --keep-session -- @docs/design.md
|
|
|
134
136
|
pi-review --continue <sessionHandle> --mode challenge --model provider/model -- "expand finding 2"
|
|
135
137
|
```
|
|
136
138
|
|
|
139
|
+
## Live Progress in AI Hosts
|
|
140
|
+
|
|
141
|
+
Agent hosts like Claude Code, Cursor, and Codex typically buffer a Bash tool's stdout until the command exits, so a multi-minute review can look like a silent wait followed by one large dump — even though `pi-review` streams by default. `--progress-log <path>` sidesteps this: it runs the child in `--mode json` and writes its event log to a file in real time, independent of what the calling tool does with stdout.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Start the review in the background, writing structured progress events to a file
|
|
145
|
+
pi-review --progress-log /tmp/pi-review.jsonl -- @src/foo.ts &
|
|
146
|
+
|
|
147
|
+
# Tail it from another process/tool (e.g. Claude Code's Monitor tool) for live updates.
|
|
148
|
+
# `message_update` and `tool_execution_update` carry high-frequency token-level deltas —
|
|
149
|
+
# excluding them keeps the feed to milestone events (tool start/end, turn end, agent end).
|
|
150
|
+
tail -f -n +1 /tmp/pi-review.jsonl | jq -c --unbuffered '
|
|
151
|
+
select(.type != "message_update" and .type != "tool_execution_update")
|
|
152
|
+
'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The JSON event schema is pi CLI's own internal format, not a contract `pi-review` guarantees — it may change between pi versions. `pi-review` parses it defensively (unparseable lines are skipped, missing events degrade to a diagnostic `parseError`) and still prints the same clean Markdown + `PI_REVIEW_META` footer to stdout once the child exits.
|
|
156
|
+
|
|
137
157
|
## CLI Reference
|
|
138
158
|
|
|
139
159
|
```
|
|
@@ -152,6 +172,8 @@ pi-review models [search]
|
|
|
152
172
|
| `--keep-session` | Persist session for follow-up |
|
|
153
173
|
| `--continue <handle>` | Continue an existing session |
|
|
154
174
|
| `--name <name>` | Session name (with `--keep-session`) |
|
|
175
|
+
| `--no-stream` | Buffer child output until exit (default: stream live) |
|
|
176
|
+
| `--progress-log <path>` | Stream child `--mode json` events to this file (cannot combine with `--no-stream`) |
|
|
155
177
|
|
|
156
178
|
## Configuration
|
|
157
179
|
|
package/README.zh-CN.md
CHANGED
|
@@ -62,7 +62,7 @@ pi-review models
|
|
|
62
62
|
|
|
63
63
|
跟进审查时,`--continue` 与首次 `/rv` 一样可**选填** `--mode`、`--model`(以及后续 CLI 支持的其它选项由 skill 直接调用 CLI 时传入)。
|
|
64
64
|
|
|
65
|
-
`/rv` 会向主会话注入**英文任务说明**(按 `--mode` 等参数区分),由主 Agent 按 **pi-review** skill 调用 `pi-review` CLI
|
|
65
|
+
`/rv` 会向主会话注入**英文任务说明**(按 `--mode` 等参数区分),由主 Agent 按 **pi-review** skill 调用 `pi-review` CLI 跑子会话。`pi-review` **默认流式转发**子进程输出,终端可实时看到审查进度;需要整段缓冲时加 `--no-stream`。若在 Claude Code / Codex 这类会缓冲 Bash 工具 stdout 的 AI host 里使用,可加 `--progress-log <path>` 把结构化进度事件实时写入文件,配合 `tail -f` 之类的工具在宿主里也能看到实时进度(见英文 README 的 “Live Progress in AI Hosts”)。Tab 补全是固定选项提示,不是另一套实现。
|
|
66
66
|
|
|
67
67
|
## 输出格式
|
|
68
68
|
|
package/dist/args.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA+C7C,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA+FpD"}
|
package/dist/args.js
CHANGED
|
@@ -15,6 +15,8 @@ Options:
|
|
|
15
15
|
--thinking <level> off|minimal|low|medium|high|xhigh
|
|
16
16
|
--skill <path> Load an extra pi skill (repeatable)
|
|
17
17
|
--tools <csv> Override allowed tools
|
|
18
|
+
--no-stream Buffer child output until exit (default: stream live)
|
|
19
|
+
--progress-log <path> Stream child --mode json events to this file (cannot combine with --no-stream)
|
|
18
20
|
-h, --help Show help
|
|
19
21
|
|
|
20
22
|
Session (requires Pi session support):
|
|
@@ -43,16 +45,16 @@ export function parseArgs(argv) {
|
|
|
43
45
|
if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help")
|
|
44
46
|
usage(0);
|
|
45
47
|
if (argv[0] === "models") {
|
|
46
|
-
return { command: "models", search: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false };
|
|
48
|
+
return { command: "models", search: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false, stream: true };
|
|
47
49
|
}
|
|
48
50
|
if (argv[0] === "install-skill") {
|
|
49
|
-
return { command: "install-skill", extraArgs: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false };
|
|
51
|
+
return { command: "install-skill", extraArgs: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false, stream: true };
|
|
50
52
|
}
|
|
51
53
|
if (argv[0] === "uninstall-skill") {
|
|
52
|
-
return { command: "uninstall-skill", extraArgs: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false };
|
|
54
|
+
return { command: "uninstall-skill", extraArgs: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false, stream: true };
|
|
53
55
|
}
|
|
54
56
|
if (argv[0] === "update") {
|
|
55
|
-
return { command: "update", mode: "code", skills: [], payload: [], keepSession: false };
|
|
57
|
+
return { command: "update", mode: "code", skills: [], payload: [], keepSession: false, stream: true };
|
|
56
58
|
}
|
|
57
59
|
let mode = "code";
|
|
58
60
|
if (argv[0] && !argv[0].startsWith("-")) {
|
|
@@ -64,6 +66,7 @@ export function parseArgs(argv) {
|
|
|
64
66
|
skills: [],
|
|
65
67
|
payload: [],
|
|
66
68
|
keepSession: false,
|
|
69
|
+
stream: true,
|
|
67
70
|
};
|
|
68
71
|
while (argv.length > 0) {
|
|
69
72
|
const arg = argv.shift();
|
|
@@ -103,6 +106,12 @@ export function parseArgs(argv) {
|
|
|
103
106
|
case "--name":
|
|
104
107
|
options.name = requireValue(arg, argv);
|
|
105
108
|
break;
|
|
109
|
+
case "--no-stream":
|
|
110
|
+
options.stream = false;
|
|
111
|
+
break;
|
|
112
|
+
case "--progress-log":
|
|
113
|
+
options.progressLog = requireValue(arg, argv);
|
|
114
|
+
break;
|
|
106
115
|
default:
|
|
107
116
|
if (arg.startsWith("--")) {
|
|
108
117
|
process.stderr.write(`pi-review: unknown option: ${arg}\n`);
|
|
@@ -117,6 +126,10 @@ export function parseArgs(argv) {
|
|
|
117
126
|
process.stderr.write("pi-review: --keep-session and --continue cannot be used together\n");
|
|
118
127
|
process.exit(2);
|
|
119
128
|
}
|
|
129
|
+
if (options.progressLog && !options.stream) {
|
|
130
|
+
process.stderr.write("pi-review: --progress-log cannot be used with --no-stream\n");
|
|
131
|
+
process.exit(2);
|
|
132
|
+
}
|
|
120
133
|
return options;
|
|
121
134
|
}
|
|
122
135
|
//# sourceMappingURL=args.js.map
|
package/dist/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAEA,SAAS,KAAK,CAAC,QAAQ,GAAG,CAAC;IACzB,MAAM,GAAG,GAAG
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ChildRunResult = {
|
|
2
|
+
status: number | null;
|
|
3
|
+
signal: NodeJS.Signals | null;
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
error?: Error;
|
|
7
|
+
};
|
|
8
|
+
export declare const DEFAULT_STREAM_CAPTURE_LIMIT: number;
|
|
9
|
+
type SpawnCommon = {
|
|
10
|
+
cwd?: string;
|
|
11
|
+
env?: NodeJS.ProcessEnv;
|
|
12
|
+
};
|
|
13
|
+
export declare function spawnBufferedChild(command: string, argv: string[], options: SpawnCommon): ChildRunResult;
|
|
14
|
+
export declare function spawnStreamingChild(command: string, argv: string[], options: SpawnCommon & {
|
|
15
|
+
stdoutSink?: NodeJS.WritableStream;
|
|
16
|
+
stderrSink?: NodeJS.WritableStream;
|
|
17
|
+
maxCaptureChars?: number;
|
|
18
|
+
}): Promise<ChildRunResult>;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=child-process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-process.d.ts","sourceRoot":"","sources":["../src/child-process.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,4BAA4B,QAAmB,CAAC;AAE7D,KAAK,WAAW,GAAG;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,CAAC;AAQF,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,WAAW,GACnB,cAAc,CAchB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,WAAW,GAAG;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,cAAc,CAAC,CAqDzB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
export const DEFAULT_STREAM_CAPTURE_LIMIT = 50 * 1024 * 1024;
|
|
3
|
+
function appendBoundedTail(current, chunk, limit) {
|
|
4
|
+
if (limit <= 0)
|
|
5
|
+
return "";
|
|
6
|
+
const next = current + chunk;
|
|
7
|
+
return next.length > limit ? next.slice(next.length - limit) : next;
|
|
8
|
+
}
|
|
9
|
+
export function spawnBufferedChild(command, argv, options) {
|
|
10
|
+
const child = spawnSync(command, argv, {
|
|
11
|
+
cwd: options.cwd,
|
|
12
|
+
env: options.env,
|
|
13
|
+
encoding: "utf8",
|
|
14
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
status: child.status,
|
|
18
|
+
signal: child.signal,
|
|
19
|
+
stdout: child.stdout || "",
|
|
20
|
+
stderr: child.stderr || "",
|
|
21
|
+
error: child.error,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export async function spawnStreamingChild(command, argv, options) {
|
|
25
|
+
const stdoutSink = options.stdoutSink ?? process.stdout;
|
|
26
|
+
const stderrSink = options.stderrSink ?? process.stderr;
|
|
27
|
+
const maxCaptureChars = options.maxCaptureChars ?? DEFAULT_STREAM_CAPTURE_LIMIT;
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const spawnOpts = {
|
|
30
|
+
cwd: options.cwd,
|
|
31
|
+
env: options.env,
|
|
32
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
33
|
+
};
|
|
34
|
+
const child = spawn(command, argv, spawnOpts);
|
|
35
|
+
let stdout = "";
|
|
36
|
+
let stderr = "";
|
|
37
|
+
let settled = false;
|
|
38
|
+
const finish = (result) => {
|
|
39
|
+
if (settled)
|
|
40
|
+
return;
|
|
41
|
+
settled = true;
|
|
42
|
+
resolve(result);
|
|
43
|
+
};
|
|
44
|
+
child.stdout?.setEncoding("utf8");
|
|
45
|
+
child.stderr?.setEncoding("utf8");
|
|
46
|
+
child.stdout?.on("data", (chunk) => {
|
|
47
|
+
stdout = appendBoundedTail(stdout, chunk, maxCaptureChars);
|
|
48
|
+
stdoutSink.write(chunk);
|
|
49
|
+
});
|
|
50
|
+
child.stderr?.on("data", (chunk) => {
|
|
51
|
+
stderr = appendBoundedTail(stderr, chunk, maxCaptureChars);
|
|
52
|
+
stderrSink.write(chunk);
|
|
53
|
+
});
|
|
54
|
+
child.on("error", (error) => {
|
|
55
|
+
finish({
|
|
56
|
+
status: null,
|
|
57
|
+
signal: null,
|
|
58
|
+
stdout,
|
|
59
|
+
stderr,
|
|
60
|
+
error,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
child.on("close", (status, signal) => {
|
|
64
|
+
finish({
|
|
65
|
+
status,
|
|
66
|
+
signal,
|
|
67
|
+
stdout,
|
|
68
|
+
stderr,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=child-process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-process.js","sourceRoot":"","sources":["../src/child-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAqB,MAAM,oBAAoB,CAAC;AAUzE,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAO7D,SAAS,iBAAiB,CAAC,OAAe,EAAE,KAAa,EAAE,KAAa;IACtE,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC;IAC7B,OAAO,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,IAAc,EACd,OAAoB;IAEpB,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE;QACrC,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,QAAQ,EAAE,MAAM;QAChB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;KAC5B,CAAC,CAAC;IACH,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE;QAC1B,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE;QAC1B,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAe,EACf,IAAc,EACd,OAIC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IACxD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IACxD,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,4BAA4B,CAAC;IAEhF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,SAAS,GAAiB;YAC9B,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC;QACF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAC9C,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,MAAM,MAAM,GAAG,CAAC,MAAsB,EAAE,EAAE;YACxC,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC;QAEF,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAClC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAElC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACzC,MAAM,GAAG,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC;YAC3D,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACzC,MAAM,GAAG,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC;YAC3D,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,MAAM,CAAC;gBACL,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,IAAI;gBACZ,MAAM;gBACN,MAAM;gBACN,KAAK;aACN,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE;YACnC,MAAM,CAAC;gBACL,MAAM;gBACN,MAAM;gBACN,MAAM;gBACN,MAAM;aACP,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-process.test.d.ts","sourceRoot":"","sources":["../src/child-process.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { Writable } from "node:stream";
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
import { spawnBufferedChild, spawnStreamingChild } from "./child-process.js";
|
|
5
|
+
function collectSink() {
|
|
6
|
+
const chunks = [];
|
|
7
|
+
const stream = new Writable({
|
|
8
|
+
write(chunk, _enc, cb) {
|
|
9
|
+
chunks.push(String(chunk));
|
|
10
|
+
cb();
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
return { stream, chunks };
|
|
14
|
+
}
|
|
15
|
+
test("spawnBufferedChild captures stdout", () => {
|
|
16
|
+
const r = spawnBufferedChild(process.execPath, ["-e", "console.log('buf')"], {});
|
|
17
|
+
assert.equal(r.status, 0);
|
|
18
|
+
assert.match(r.stdout, /buf/);
|
|
19
|
+
});
|
|
20
|
+
test("spawnStreamingChild reports signal when child is killed", async () => {
|
|
21
|
+
const { stream } = collectSink();
|
|
22
|
+
const r = await spawnStreamingChild(process.execPath, ["-e", "process.kill(process.pid, 'SIGTERM')"], { stdoutSink: stream, stderrSink: stream });
|
|
23
|
+
assert.equal(r.status, null);
|
|
24
|
+
assert.equal(r.signal, "SIGTERM");
|
|
25
|
+
});
|
|
26
|
+
test("spawnStreamingChild forwards chunks and collects full stdout", async () => {
|
|
27
|
+
const script = [
|
|
28
|
+
"console.log('line1');",
|
|
29
|
+
"setTimeout(() => console.log('line2'), 30);",
|
|
30
|
+
].join("");
|
|
31
|
+
const { stream, chunks } = collectSink();
|
|
32
|
+
const r = await spawnStreamingChild(process.execPath, ["-e", script], {
|
|
33
|
+
stdoutSink: stream,
|
|
34
|
+
stderrSink: stream,
|
|
35
|
+
});
|
|
36
|
+
assert.equal(r.status, 0);
|
|
37
|
+
assert.match(r.stdout, /line1/);
|
|
38
|
+
assert.match(r.stdout, /line2/);
|
|
39
|
+
const forwarded = chunks.join("");
|
|
40
|
+
assert.match(forwarded, /line1/);
|
|
41
|
+
assert.match(forwarded, /line2/);
|
|
42
|
+
});
|
|
43
|
+
test("spawnStreamingChild keeps captured stdout bounded while forwarding all output", async () => {
|
|
44
|
+
const script = "process.stdout.write('abcde'); process.stdout.write('fghij');";
|
|
45
|
+
const { stream, chunks } = collectSink();
|
|
46
|
+
const r = await spawnStreamingChild(process.execPath, ["-e", script], {
|
|
47
|
+
stdoutSink: stream,
|
|
48
|
+
stderrSink: stream,
|
|
49
|
+
maxCaptureChars: 6,
|
|
50
|
+
});
|
|
51
|
+
assert.equal(r.status, 0);
|
|
52
|
+
assert.equal(r.stdout, "efghij");
|
|
53
|
+
assert.equal(chunks.join(""), "abcdefghij");
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=child-process.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-process.test.js","sourceRoot":"","sources":["../src/child-process.test.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhD,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC;KAAM,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;IACvC,SAAS,EAAE,CAAC;AACd,CAAC;KAAM,IAAI,MAAM,CAAC,OAAO,KAAK,eAAe,EAAE,CAAC;IAC9C,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AACjC,CAAC;KAAM,IAAI,MAAM,CAAC,OAAO,KAAK,iBAAiB,EAAE,CAAC;IAChD,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;KAAM,CAAC;IACN,SAAS,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhD,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC;KAAM,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;IACvC,SAAS,EAAE,CAAC;AACd,CAAC;KAAM,IAAI,MAAM,CAAC,OAAO,KAAK,eAAe,EAAE,CAAC;IAC9C,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AACjC,CAAC;KAAM,IAAI,MAAM,CAAC,OAAO,KAAK,iBAAiB,EAAE,CAAC;IAChD,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;KAAM,CAAC;IACN,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-events.d.ts","sourceRoot":"","sources":["../src/json-events.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAcD,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,kBAAkB,CAsD1E"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Parses pi's `--mode json` event stream. That format is owned by the pi
|
|
2
|
+
// binary, not a contract pi-review guarantees, so parsing degrades
|
|
3
|
+
// gracefully (never throws) when lines are malformed or events are missing.
|
|
4
|
+
function textFromMessage(message) {
|
|
5
|
+
if (!message || !Array.isArray(message.content))
|
|
6
|
+
return "";
|
|
7
|
+
return message.content
|
|
8
|
+
.filter((block) => block?.type === "text" && typeof block.text === "string")
|
|
9
|
+
.map((block) => block.text)
|
|
10
|
+
.join("");
|
|
11
|
+
}
|
|
12
|
+
function isFatalStop(message) {
|
|
13
|
+
return message.stopReason === "error" || message.stopReason === "aborted";
|
|
14
|
+
}
|
|
15
|
+
export function extractFinalText(jsonLinesText) {
|
|
16
|
+
let lastAssistantMessage;
|
|
17
|
+
let sawAgentEnd = false;
|
|
18
|
+
for (const line of jsonLinesText.split("\n")) {
|
|
19
|
+
if (!line.trim())
|
|
20
|
+
continue;
|
|
21
|
+
let event;
|
|
22
|
+
try {
|
|
23
|
+
event = JSON.parse(line);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (event?.message?.role === "assistant") {
|
|
29
|
+
lastAssistantMessage = event.message;
|
|
30
|
+
}
|
|
31
|
+
if (event?.type === "agent_end" && Array.isArray(event.messages)) {
|
|
32
|
+
sawAgentEnd = true;
|
|
33
|
+
for (let i = event.messages.length - 1; i >= 0; i--) {
|
|
34
|
+
const message = event.messages[i];
|
|
35
|
+
if (message?.role === "assistant") {
|
|
36
|
+
lastAssistantMessage = message;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!lastAssistantMessage) {
|
|
43
|
+
return { text: "", error: "no assistant message found in --mode json output" };
|
|
44
|
+
}
|
|
45
|
+
if (isFatalStop(lastAssistantMessage)) {
|
|
46
|
+
return {
|
|
47
|
+
text: textFromMessage(lastAssistantMessage),
|
|
48
|
+
error: lastAssistantMessage.errorMessage ||
|
|
49
|
+
`assistant turn ended with stopReason=${lastAssistantMessage.stopReason}`,
|
|
50
|
+
fatal: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const text = textFromMessage(lastAssistantMessage);
|
|
54
|
+
if (!text) {
|
|
55
|
+
return { text: "", error: "final assistant message had no text content" };
|
|
56
|
+
}
|
|
57
|
+
if (!sawAgentEnd) {
|
|
58
|
+
return { text, error: "no agent_end event found; used last streamed assistant message as a fallback" };
|
|
59
|
+
}
|
|
60
|
+
return { text };
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=json-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-events.js","sourceRoot":"","sources":["../src/json-events.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,mEAAmE;AACnE,4EAA4E;AAe5E,SAAS,eAAe,CAAC,OAAyC;IAChE,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAC3D,OAAO,OAAO,CAAC,OAAO;SACnB,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,MAAM,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;SAC3E,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1B,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,OAA6B;IAChD,OAAO,OAAO,CAAC,UAAU,KAAK,OAAO,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,aAAqB;IACpD,IAAI,oBAAsD,CAAC;IAC3D,IAAI,WAAW,GAAG,KAAK,CAAC;IAExB,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,SAAS;QAE3B,IAAI,KAA2F,CAAC;QAChG,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,IAAI,KAAK,EAAE,OAAO,EAAE,IAAI,KAAK,WAAW,EAAE,CAAC;YACzC,oBAAoB,GAAG,KAAK,CAAC,OAAO,CAAC;QACvC,CAAC;QAED,IAAI,KAAK,EAAE,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjE,WAAW,GAAG,IAAI,CAAC;YACnB,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBACpD,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAClC,IAAI,OAAO,EAAE,IAAI,KAAK,WAAW,EAAE,CAAC;oBAClC,oBAAoB,GAAG,OAAO,CAAC;oBAC/B,MAAM;gBACR,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,kDAAkD,EAAE,CAAC;IACjF,CAAC;IAED,IAAI,WAAW,CAAC,oBAAoB,CAAC,EAAE,CAAC;QACtC,OAAO;YACL,IAAI,EAAE,eAAe,CAAC,oBAAoB,CAAC;YAC3C,KAAK,EACH,oBAAoB,CAAC,YAAY;gBACjC,wCAAwC,oBAAoB,CAAC,UAAU,EAAE;YAC3E,KAAK,EAAE,IAAI;SACZ,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,eAAe,CAAC,oBAAoB,CAAC,CAAC;IACnD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,6CAA6C,EAAE,CAAC;IAC5E,CAAC;IAED,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,8EAA8E,EAAE,CAAC;IACzG,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-events.test.d.ts","sourceRoot":"","sources":["../src/json-events.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { extractFinalText } from "./json-events.js";
|
|
4
|
+
function lines(...events) {
|
|
5
|
+
return events.map((e) => JSON.stringify(e)).join("\n");
|
|
6
|
+
}
|
|
7
|
+
test("extractFinalText reads the final assistant text from agent_end", () => {
|
|
8
|
+
const input = lines({ type: "session", id: "s1" }, { type: "agent_start" }, { type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "partial" }] } }, {
|
|
9
|
+
type: "agent_end",
|
|
10
|
+
messages: [
|
|
11
|
+
{ role: "user", content: [{ type: "text", text: "review this" }] },
|
|
12
|
+
{
|
|
13
|
+
role: "assistant",
|
|
14
|
+
content: [
|
|
15
|
+
{ type: "text", text: "## Verdict\napprove\n\n" },
|
|
16
|
+
{ type: "text", text: "## Summary\nlooks good" },
|
|
17
|
+
],
|
|
18
|
+
stopReason: "stop",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
});
|
|
22
|
+
const result = extractFinalText(input);
|
|
23
|
+
assert.equal(result.text, "## Verdict\napprove\n\n## Summary\nlooks good");
|
|
24
|
+
assert.equal(result.error, undefined);
|
|
25
|
+
assert.equal(result.fatal, undefined);
|
|
26
|
+
});
|
|
27
|
+
test("extractFinalText skips unparseable and empty lines", () => {
|
|
28
|
+
const good = lines({
|
|
29
|
+
type: "agent_end",
|
|
30
|
+
messages: [{ role: "assistant", content: [{ type: "text", text: "ok" }], stopReason: "stop" }],
|
|
31
|
+
});
|
|
32
|
+
const input = ["", "not json {{{", good, "", "}} also not json"].join("\n");
|
|
33
|
+
const result = extractFinalText(input);
|
|
34
|
+
assert.equal(result.text, "ok");
|
|
35
|
+
assert.equal(result.error, undefined);
|
|
36
|
+
});
|
|
37
|
+
test("extractFinalText falls back to the last streamed assistant message when agent_end is missing", () => {
|
|
38
|
+
const input = lines({ type: "message_update", message: { role: "assistant", content: [{ type: "text", text: "first" }] } }, { type: "message_update", message: { role: "assistant", content: [{ type: "text", text: "first final" }] } });
|
|
39
|
+
const result = extractFinalText(input);
|
|
40
|
+
assert.equal(result.text, "first final");
|
|
41
|
+
assert.match(result.error ?? "", /agent_end/);
|
|
42
|
+
assert.equal(result.fatal, undefined);
|
|
43
|
+
});
|
|
44
|
+
test("extractFinalText reports an error when no assistant content exists at all", () => {
|
|
45
|
+
const input = lines({
|
|
46
|
+
type: "agent_end",
|
|
47
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
48
|
+
});
|
|
49
|
+
const result = extractFinalText(input);
|
|
50
|
+
assert.equal(result.text, "");
|
|
51
|
+
assert.ok(result.error);
|
|
52
|
+
});
|
|
53
|
+
test("extractFinalText marks a fatal stop reason", () => {
|
|
54
|
+
const input = lines({
|
|
55
|
+
type: "agent_end",
|
|
56
|
+
messages: [
|
|
57
|
+
{
|
|
58
|
+
role: "assistant",
|
|
59
|
+
content: [{ type: "text", text: "" }],
|
|
60
|
+
stopReason: "error",
|
|
61
|
+
errorMessage: "provider rate limited",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
const result = extractFinalText(input);
|
|
66
|
+
assert.equal(result.fatal, true);
|
|
67
|
+
assert.equal(result.error, "provider rate limited");
|
|
68
|
+
});
|
|
69
|
+
test("extractFinalText handles empty input without throwing", () => {
|
|
70
|
+
const result = extractFinalText("");
|
|
71
|
+
assert.equal(result.text, "");
|
|
72
|
+
assert.ok(result.error);
|
|
73
|
+
});
|
|
74
|
+
test("extractFinalText uses the last agent_end when the child retries", () => {
|
|
75
|
+
const input = lines({
|
|
76
|
+
type: "agent_end",
|
|
77
|
+
willRetry: true,
|
|
78
|
+
messages: [{ role: "assistant", content: [{ type: "text", text: "first attempt" }], stopReason: "error" }],
|
|
79
|
+
}, {
|
|
80
|
+
type: "agent_end",
|
|
81
|
+
willRetry: false,
|
|
82
|
+
messages: [{ role: "assistant", content: [{ type: "text", text: "final attempt" }], stopReason: "stop" }],
|
|
83
|
+
});
|
|
84
|
+
const result = extractFinalText(input);
|
|
85
|
+
assert.equal(result.text, "final attempt");
|
|
86
|
+
assert.equal(result.fatal, undefined);
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=json-events.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-events.test.js","sourceRoot":"","sources":["../src/json-events.test.ts"],"names":[],"mappings":"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"}
|
package/dist/review.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { ParsedArgs } from "./types.js";
|
|
2
|
+
import { type ChildRunResult } from "./child-process.js";
|
|
3
|
+
/** Ensures PI_REVIEW_META is on its own line after streamed child stdout. */
|
|
4
|
+
export declare function metaLinePrefix(childStdout: string, streamMode: boolean): string;
|
|
5
|
+
/** True when the final text must be printed after exit instead of having streamed live. */
|
|
6
|
+
export declare function progressLogBuffersOutput(streamMode: boolean, hasProgressLog: boolean): boolean;
|
|
7
|
+
export declare function childRuntimeError(child: Pick<ChildRunResult, "status" | "signal" | "error">): string | undefined;
|
|
2
8
|
export declare function runModels(piBin: string, args: string[]): never;
|
|
3
|
-
export declare function runReview(parsed: ParsedArgs): void
|
|
9
|
+
export declare function runReview(parsed: ParsedArgs): Promise<void>;
|
|
4
10
|
//# sourceMappingURL=review.d.ts.map
|
package/dist/review.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,YAAY,CAAC;AACzD,OAAO,EAA2C,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAkClG,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAG/E;AAED,2FAA2F;AAC3F,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,GAAG,OAAO,CAE9F;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,CAKhH;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAM9D;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAsIjE"}
|
package/dist/review.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { spawnBufferedChild, spawnStreamingChild } from "./child-process.js";
|
|
4
6
|
import { loadPresets, loadSystemPrompt } from "./presets.js";
|
|
5
7
|
import { splitPayload, buildPrompt } from "./prompt.js";
|
|
6
8
|
import { parseVerdict } from "./verdict.js";
|
|
9
|
+
import { extractFinalText } from "./json-events.js";
|
|
7
10
|
import { makeRunSessionDir, newestJsonl } from "./session.js";
|
|
8
11
|
import { fail, hasPathSeparator, expandMaybeHome, normalizeTools } from "./utils.js";
|
|
9
12
|
import { resolveConfig } from "./config.js";
|
|
@@ -11,7 +14,6 @@ function readStdin() {
|
|
|
11
14
|
if (process.stdin.isTTY)
|
|
12
15
|
return "";
|
|
13
16
|
try {
|
|
14
|
-
const fs = require("node:fs");
|
|
15
17
|
return fs.readFileSync(0, "utf8").trim();
|
|
16
18
|
}
|
|
17
19
|
catch {
|
|
@@ -32,6 +34,25 @@ function childEnv(piBin) {
|
|
|
32
34
|
PATH: [...new Set(pathEntries.join(":").split(":").filter(Boolean))].join(":"),
|
|
33
35
|
};
|
|
34
36
|
}
|
|
37
|
+
/** Ensures PI_REVIEW_META is on its own line after streamed child stdout. */
|
|
38
|
+
export function metaLinePrefix(childStdout, streamMode) {
|
|
39
|
+
if (!streamMode || !childStdout)
|
|
40
|
+
return "";
|
|
41
|
+
return childStdout.endsWith("\n") ? "" : "\n";
|
|
42
|
+
}
|
|
43
|
+
/** True when the final text must be printed after exit instead of having streamed live. */
|
|
44
|
+
export function progressLogBuffersOutput(streamMode, hasProgressLog) {
|
|
45
|
+
return !streamMode || hasProgressLog;
|
|
46
|
+
}
|
|
47
|
+
export function childRuntimeError(child) {
|
|
48
|
+
if (child.error)
|
|
49
|
+
return child.error.message;
|
|
50
|
+
if (child.signal)
|
|
51
|
+
return `child pi terminated by signal ${child.signal}`;
|
|
52
|
+
if ((child.status ?? 0) !== 0)
|
|
53
|
+
return `child pi exited with status ${child.status}`;
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
35
56
|
export function runModels(piBin, args) {
|
|
36
57
|
const result = spawnSync(piBin, ["--list-models", ...args], {
|
|
37
58
|
stdio: "inherit",
|
|
@@ -39,7 +60,7 @@ export function runModels(piBin, args) {
|
|
|
39
60
|
});
|
|
40
61
|
process.exit(result.status ?? (result.error ? 1 : 0));
|
|
41
62
|
}
|
|
42
|
-
export function runReview(parsed) {
|
|
63
|
+
export async function runReview(parsed) {
|
|
43
64
|
const config = resolveConfig();
|
|
44
65
|
const presets = loadPresets(config.presetsFile);
|
|
45
66
|
const preset = presets[parsed.mode];
|
|
@@ -50,6 +71,8 @@ export function runReview(parsed) {
|
|
|
50
71
|
const payload = splitPayload(parsed.payload);
|
|
51
72
|
const prompt = buildPrompt(parsed.mode, preset, payload, stdinText);
|
|
52
73
|
const args = ["-p"];
|
|
74
|
+
if (parsed.progressLog)
|
|
75
|
+
args.push("--mode", "json");
|
|
53
76
|
const systemPrompt = loadSystemPrompt(config.systemPromptFile);
|
|
54
77
|
if (systemPrompt)
|
|
55
78
|
args.push("--append-system-prompt", systemPrompt);
|
|
@@ -81,29 +104,76 @@ export function runReview(parsed) {
|
|
|
81
104
|
args.push("--no-session");
|
|
82
105
|
}
|
|
83
106
|
args.push(...payload.fileRefs, prompt);
|
|
107
|
+
let progressStream;
|
|
108
|
+
let progressStreamError;
|
|
109
|
+
if (parsed.progressLog) {
|
|
110
|
+
try {
|
|
111
|
+
fs.mkdirSync(path.dirname(parsed.progressLog), { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
fail(`failed to prepare --progress-log directory: ${error.message}`);
|
|
115
|
+
}
|
|
116
|
+
progressStream = fs.createWriteStream(parsed.progressLog, { flags: "a" });
|
|
117
|
+
progressStream.on("error", (error) => {
|
|
118
|
+
progressStreamError = error;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
84
121
|
const startedAt = Date.now();
|
|
85
|
-
const
|
|
122
|
+
const spawnOpts = {
|
|
86
123
|
cwd: process.cwd(),
|
|
87
124
|
env: childEnv(config.piBin),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
125
|
+
...(progressStream ? { stdoutSink: progressStream } : {}),
|
|
126
|
+
};
|
|
127
|
+
const child = parsed.stream
|
|
128
|
+
? await spawnStreamingChild(config.piBin, args, spawnOpts)
|
|
129
|
+
: spawnBufferedChild(config.piBin, args, spawnOpts);
|
|
91
130
|
const durationMs = Date.now() - startedAt;
|
|
92
|
-
|
|
131
|
+
if (progressStream) {
|
|
132
|
+
await new Promise((resolve) => progressStream.end(() => resolve()));
|
|
133
|
+
if (progressStreamError) {
|
|
134
|
+
process.stderr.write(`pi-review: warning: --progress-log write failed: ${progressStreamError.message}\n`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
let stdout = child.stdout || "";
|
|
93
138
|
const stderr = child.stderr || "";
|
|
94
|
-
|
|
139
|
+
let extractedError;
|
|
140
|
+
let extractedFatal = false;
|
|
141
|
+
if (parsed.progressLog) {
|
|
142
|
+
const extracted = extractFinalText(stdout);
|
|
143
|
+
stdout = extracted.text;
|
|
144
|
+
extractedError = extracted.error;
|
|
145
|
+
extractedFatal = Boolean(extracted.fatal);
|
|
146
|
+
}
|
|
147
|
+
const bufferedPrint = progressLogBuffersOutput(parsed.stream, Boolean(parsed.progressLog));
|
|
148
|
+
if (bufferedPrint && stdout) {
|
|
95
149
|
process.stdout.write(stdout.endsWith("\n") ? stdout : `${stdout}\n`);
|
|
96
|
-
|
|
150
|
+
}
|
|
151
|
+
if (!parsed.stream && stderr) {
|
|
97
152
|
process.stderr.write(stderr.endsWith("\n") ? stderr : `${stderr}\n`);
|
|
153
|
+
}
|
|
98
154
|
if (parsed.keepSession) {
|
|
99
155
|
sessionHandle = newestJsonl(runSessionDir);
|
|
100
156
|
}
|
|
101
157
|
let verdictInfo = parseVerdict(stdout);
|
|
102
|
-
|
|
158
|
+
const runtimeError = childRuntimeError(child);
|
|
159
|
+
if (runtimeError) {
|
|
103
160
|
verdictInfo = {
|
|
104
161
|
verdict: "blocked",
|
|
105
162
|
verdictSource: "runtime_error",
|
|
106
|
-
parseError:
|
|
163
|
+
parseError: runtimeError,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
else if (extractedFatal) {
|
|
167
|
+
verdictInfo = {
|
|
168
|
+
verdict: "blocked",
|
|
169
|
+
verdictSource: "runtime_error",
|
|
170
|
+
parseError: extractedError,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
else if (extractedError) {
|
|
174
|
+
verdictInfo = {
|
|
175
|
+
...verdictInfo,
|
|
176
|
+
parseError: [verdictInfo.parseError, extractedError].filter(Boolean).join("; "),
|
|
107
177
|
};
|
|
108
178
|
}
|
|
109
179
|
const meta = {
|
|
@@ -115,7 +185,10 @@ export function runReview(parsed) {
|
|
|
115
185
|
sessionHandle: sessionHandle || undefined,
|
|
116
186
|
parseError: verdictInfo.parseError || undefined,
|
|
117
187
|
};
|
|
188
|
+
const metaPrefix = metaLinePrefix(stdout, !bufferedPrint);
|
|
189
|
+
if (metaPrefix)
|
|
190
|
+
process.stdout.write(metaPrefix);
|
|
118
191
|
process.stdout.write(`PI_REVIEW_META: ${JSON.stringify(meta)}\n`);
|
|
119
|
-
process.exit(child.status ?? (child.error ? 1 : 0));
|
|
192
|
+
process.exit(child.status ?? (child.error || child.signal ? 1 : 0));
|
|
120
193
|
}
|
|
121
194
|
//# sourceMappingURL=review.js.map
|
package/dist/review.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.js","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,SAAS,SAAS;IAChB,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.meta.test.d.ts","sourceRoot":"","sources":["../src/review.meta.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { childRuntimeError, metaLinePrefix, progressLogBuffersOutput } from "./review.js";
|
|
4
|
+
test("metaLinePrefix adds newline when streamed stdout lacks trailing newline", () => {
|
|
5
|
+
assert.equal(metaLinePrefix("hello", true), "\n");
|
|
6
|
+
assert.equal(metaLinePrefix("hello\n", true), "");
|
|
7
|
+
assert.equal(metaLinePrefix("", true), "");
|
|
8
|
+
});
|
|
9
|
+
test("metaLinePrefix is empty in buffered mode", () => {
|
|
10
|
+
assert.equal(metaLinePrefix("hello", false), "");
|
|
11
|
+
});
|
|
12
|
+
test("childRuntimeError treats spawn errors as runtime errors", () => {
|
|
13
|
+
assert.equal(childRuntimeError({ status: null, signal: null, error: new Error("spawn ENOENT") }), "spawn ENOENT");
|
|
14
|
+
});
|
|
15
|
+
test("progressLogBuffersOutput buffers whenever streaming is off or a progress log is set", () => {
|
|
16
|
+
assert.equal(progressLogBuffersOutput(true, true), true);
|
|
17
|
+
assert.equal(progressLogBuffersOutput(true, false), false);
|
|
18
|
+
assert.equal(progressLogBuffersOutput(false, false), true);
|
|
19
|
+
assert.equal(progressLogBuffersOutput(false, true), true);
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=review.meta.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.meta.test.js","sourceRoot":"","sources":["../src/review.meta.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAE1F,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;IACnF,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;AAC7C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACpD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACnE,MAAM,CAAC,KAAK,CACV,iBAAiB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,EACnF,cAAc,CACf,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qFAAqF,EAAE,GAAG,EAAE;IAC/F,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IACzD,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC3D,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;IAC3D,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;AAC5D,CAAC,CAAC,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export interface ParsedArgs {
|
|
|
7
7
|
skills: string[];
|
|
8
8
|
payload: string[];
|
|
9
9
|
keepSession: boolean;
|
|
10
|
+
/** When false, buffer child output and print after exit (legacy / scripting). Default: stream. */
|
|
11
|
+
stream: boolean;
|
|
12
|
+
/** When set, run the child in --mode json and stream its event log to this file path. */
|
|
13
|
+
progressLog?: string;
|
|
10
14
|
continueHandle?: string;
|
|
11
15
|
model?: string;
|
|
12
16
|
provider?: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,2EAA4E,CAAC;AAClG,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,eAAe,GAAG,iBAAiB,GAAG,QAAQ,CAAC;IAC9E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,QAAQ,GAAG,UAAU,GAAG,eAAe,CAAC;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,2EAA4E,CAAC;AAClG,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,eAAe,GAAG,iBAAiB,GAAG,QAAQ,CAAC;IAC9E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,kGAAkG;IAClG,MAAM,EAAE,OAAO,CAAC;IAChB,yFAAyF;IACzF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,QAAQ,GAAG,UAAU,GAAG,eAAe,CAAC;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB"}
|
package/extensions/review.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
export default function piReviewExtension(pi: ExtensionAPI) {
|
|
10
10
|
pi.registerCommand("rv", {
|
|
11
11
|
description:
|
|
12
|
-
"Delegate pi-review to the agent. Usage: /rv [--mode plan|challenge] [--model id] [--keep-session] @target | /rv --continue <handle> [opts] [text] | models",
|
|
12
|
+
"Delegate pi-review to the agent. Usage: /rv [--mode plan|challenge] [--model id] [--keep-session] [--no-stream] @target | /rv --continue <handle> [opts] [text] | models",
|
|
13
13
|
getArgumentCompletions: (prefix) => {
|
|
14
14
|
const filtered = RV_COMPLETIONS.filter((item) => item.value.startsWith(prefix));
|
|
15
15
|
return filtered.length
|
|
@@ -25,6 +25,18 @@ describe("parseRvArgs / validateRvParsed", () => {
|
|
|
25
25
|
assert.equal(v.ok, false);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
it("parses --no-stream into CLI argv", () => {
|
|
29
|
+
const p = parseRvArgs("--no-stream @src/foo.ts");
|
|
30
|
+
assert.equal(p.noStream, true);
|
|
31
|
+
assert.equal(p.target, "@src/foo.ts");
|
|
32
|
+
assert.deepEqual(buildPiReviewArgv(p, "@src/foo.ts"), [
|
|
33
|
+
"pi-review",
|
|
34
|
+
"--no-stream",
|
|
35
|
+
"--",
|
|
36
|
+
"@src/foo.ts",
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
|
|
28
40
|
it("buildPiReviewArgv matches for initial and continue", () => {
|
|
29
41
|
const initial = parseRvArgs("--mode plan --model m/a @doc.md");
|
|
30
42
|
assert.deepEqual(buildPiReviewArgv(initial, "@doc.md"), [
|
package/extensions/rv-prompts.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type RvParsed = {
|
|
|
9
9
|
mode: string;
|
|
10
10
|
model?: string;
|
|
11
11
|
keepSession: boolean;
|
|
12
|
+
noStream: boolean;
|
|
12
13
|
continueHandle?: string;
|
|
13
14
|
modelsOnly: boolean;
|
|
14
15
|
target: string;
|
|
@@ -25,6 +26,7 @@ export function parseRvArgs(raw: string): RvParsed {
|
|
|
25
26
|
let mode = "code";
|
|
26
27
|
let model: string | undefined;
|
|
27
28
|
let keepSession = false;
|
|
29
|
+
let noStream = false;
|
|
28
30
|
let continueHandle: string | undefined;
|
|
29
31
|
let modelsOnly = false;
|
|
30
32
|
const rest: string[] = [];
|
|
@@ -39,6 +41,10 @@ export function parseRvArgs(raw: string): RvParsed {
|
|
|
39
41
|
keepSession = true;
|
|
40
42
|
continue;
|
|
41
43
|
}
|
|
44
|
+
if (t === "--no-stream") {
|
|
45
|
+
noStream = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
42
48
|
if (t === "--mode") {
|
|
43
49
|
const m = cleaned[i + 1];
|
|
44
50
|
if (!m || m.startsWith("--")) {
|
|
@@ -73,6 +79,7 @@ export function parseRvArgs(raw: string): RvParsed {
|
|
|
73
79
|
mode,
|
|
74
80
|
model,
|
|
75
81
|
keepSession,
|
|
82
|
+
noStream,
|
|
76
83
|
continueHandle,
|
|
77
84
|
modelsOnly,
|
|
78
85
|
target: rest.join(" ").trim(),
|
|
@@ -101,6 +108,7 @@ export function buildPiReviewArgv(parsed: RvParsed, target: string): string[] {
|
|
|
101
108
|
}
|
|
102
109
|
if (parsed.mode !== "code") parts.push("--mode", parsed.mode);
|
|
103
110
|
if (parsed.model) parts.push("--model", parsed.model);
|
|
111
|
+
if (parsed.noStream) parts.push("--no-stream");
|
|
104
112
|
parts.push("--", target);
|
|
105
113
|
return parts;
|
|
106
114
|
}
|
|
@@ -111,6 +119,7 @@ function baseRules(): string {
|
|
|
111
119
|
"Run pi-review in an isolated child session; return only the review conclusion.",
|
|
112
120
|
"Do not edit, patch, commit, or implement findings unless the user asks separately.",
|
|
113
121
|
"Preserve the final PI_REVIEW_META line from pi-review output.",
|
|
122
|
+
"pi-review streams child output by default so the user sees live progress; use --no-stream only if buffering is required.",
|
|
114
123
|
].join(" ");
|
|
115
124
|
}
|
|
116
125
|
|
|
@@ -131,7 +140,7 @@ export function buildRvOrchestrationPrompt(parsed: RvParsed): string {
|
|
|
131
140
|
if (!target && !parsed.continueHandle) {
|
|
132
141
|
return [
|
|
133
142
|
"/rv needs a review target.",
|
|
134
|
-
"Usage: /rv [--mode code|plan|challenge] [--model provider/model] [--keep-session] @files-or-brief",
|
|
143
|
+
"Usage: /rv [--mode code|plan|challenge] [--model provider/model] [--keep-session] [--no-stream] @files-or-brief",
|
|
135
144
|
"Continue: /rv --continue <handle> [--mode ...] [--model ...] [follow-up text]",
|
|
136
145
|
"Examples: /rv @src/foo.ts | /rv --mode challenge @docs/design.md | /rv models",
|
|
137
146
|
].join("\n\n");
|
|
@@ -188,5 +197,6 @@ export const RV_COMPLETIONS: { value: string; hint: string }[] = [
|
|
|
188
197
|
{ value: "--mode challenge", hint: "adversarial plan review" },
|
|
189
198
|
{ value: "--model ", hint: "provider/model from pi-review models" },
|
|
190
199
|
{ value: "--keep-session", hint: "persist session for follow-up" },
|
|
200
|
+
{ value: "--no-stream", hint: "buffer child output until exit" },
|
|
191
201
|
{ value: "--continue ", hint: "resume session; same optional flags as initial /rv" },
|
|
192
202
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zephyrdeng/pi-review",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
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>",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"build": "tsc",
|
|
68
68
|
"check": "tsc --noEmit",
|
|
69
69
|
"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/json-events.test.ts extensions/rv-prompts.test.ts",
|
|
70
71
|
"test:rv": "npx --yes tsx --test extensions/rv-prompts.test.ts",
|
|
71
72
|
"prepublishOnly": "npm run build",
|
|
72
73
|
"pack:dry": "npm pack --dry-run"
|
|
@@ -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. Use when the user asks for Pi review, isolated review, code review, plan review, challenge review, or wants to avoid context pollution.
|
|
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. 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.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Pi Review
|
|
@@ -23,6 +23,36 @@ node ../../bin/pi-review.js --help
|
|
|
23
23
|
|
|
24
24
|
When resolving this fallback, use the actual directory that contains this `SKILL.md`.
|
|
25
25
|
|
|
26
|
+
## Streaming vs agent hosts
|
|
27
|
+
|
|
28
|
+
- `pi-review` **streams** child `pi` stdout/stderr to the process terminal by default (`--no-stream` buffers until exit).
|
|
29
|
+
- **Pi** with `/rv` or a foreground bash that inherits stdio: users often see output arrive live.
|
|
30
|
+
- **Claude Code, Cursor, Codex**, and similar hosts usually **buffer tool stdout** until the bash command exits, so the chat may show the review **all at once** even though the CLI is streaming. That is expected—not a misconfiguration. Also note: pi-review's default `text` mode produces **no output at all** during the tool-use/thinking phase (only the final answer), so even a local terminal looks silent for most of a multi-minute review.
|
|
31
|
+
- For **live** progress in those hosts, prefer `--progress-log <path>`: it runs the child in `--mode json` and writes a structured, incremental event log to that file, independent of host stdout buffering. Run `pi-review --progress-log <path> ...` with a background/async tool call, then tail the file (e.g. with a Monitor-style tool) filtering out `message_update`/`tool_execution_update` (high-frequency token deltas) to surface milestone events. Fall back to a **local terminal** running the same command only if the host has no way to run a background command or tail a file.
|
|
32
|
+
|
|
33
|
+
## Review status (no slash command)
|
|
34
|
+
|
|
35
|
+
There is **no** `/status` slash in this skill. When the user asks for **review status**, **progress**, **is pi-review still running**, or **/status** in the sense of review progress, run read-only checks and summarize—do **not** start a new review.
|
|
36
|
+
|
|
37
|
+
1. **In-flight review (heuristic)** — list likely `pi-review` / child `pi -p` processes:
|
|
38
|
+
```bash
|
|
39
|
+
pgrep -fl 'pi-review' 2>/dev/null || true
|
|
40
|
+
pgrep -fl 'pi.*-p' 2>/dev/null | head -20 || true
|
|
41
|
+
```
|
|
42
|
+
If matches exist, say a review **may still be running** (heuristic; other `pi -p` jobs can match). If none, say **no obvious in-flight pi-review** from process list—not that the last review failed.
|
|
43
|
+
|
|
44
|
+
2. **Persisted sessions** (only after `--keep-session` / `--continue`) — default dir `~/.pi/pi-review/sessions`, overridable with `PI_REVIEW_SESSION_DIR`:
|
|
45
|
+
```bash
|
|
46
|
+
ls -lt "${PI_REVIEW_SESSION_DIR:-$HOME/.pi/pi-review/sessions}" 2>/dev/null | head -15 || true
|
|
47
|
+
```
|
|
48
|
+
Mention newest folders and that `sessionHandle` from `PI_REVIEW_META` is used with `--continue`.
|
|
49
|
+
|
|
50
|
+
3. **Last conclusion** — if this conversation already has a `PI_REVIEW_META` line, quote `verdict`, `durationMs`, and `sessionHandle` instead of re-running review.
|
|
51
|
+
|
|
52
|
+
4. **Live output** — remind that chat tools may not stream; prefer re-running with `--progress-log <path>` + a background/tail workflow for live progress, or use a **terminal** running `pi-review` (default streaming) as a fallback.
|
|
53
|
+
|
|
54
|
+
Do not implement findings from a status check; status is informational only.
|
|
55
|
+
|
|
26
56
|
## Steps
|
|
27
57
|
|
|
28
58
|
1. Prime the model catalog:
|
|
@@ -43,7 +73,7 @@ When resolving this fallback, use the actual directory that contains this `SKILL
|
|
|
43
73
|
|
|
44
74
|
3. Run an isolated review:
|
|
45
75
|
```bash
|
|
46
|
-
pi-review [--mode <name>] [--model <provider/model[:thinking]>] [--keep-session|--continue <handle>] -- <@files|text...>
|
|
76
|
+
pi-review [--mode <name>] [--model <provider/model[:thinking]>] [--keep-session|--continue <handle>] [--progress-log <path>] -- <@files|text...>
|
|
47
77
|
```
|
|
48
78
|
Rules:
|
|
49
79
|
- Use the model decision from step 1; do not invent model IDs.
|
|
@@ -52,6 +82,8 @@ When resolving this fallback, use the actual directory that contains this `SKILL
|
|
|
52
82
|
- Use `--continue <handle>` only with a previous `PI_REVIEW_META.sessionHandle`.
|
|
53
83
|
- Put file references as `@path` after `--`.
|
|
54
84
|
- Do not ask `pi-review` to edit, patch, commit, or implement.
|
|
85
|
+
- Default streams child output live; add `--no-stream` only when the caller must buffer until exit (cannot combine with `--progress-log`).
|
|
86
|
+
- In a host that buffers tool stdout (Claude Code, Codex, ...), prefer running with `--progress-log <path>` in the background and tailing the file for live progress instead of waiting on the blocking call.
|
|
55
87
|
Completion criterion: the command includes the resolved model/default choice and a concrete review target.
|
|
56
88
|
|
|
57
89
|
4. Return the result:
|
|
@@ -67,3 +99,8 @@ pi-review --model openai/gpt-5.5 -- @src/foo.ts
|
|
|
67
99
|
pi-review --mode challenge --keep-session -- @docs/design.md
|
|
68
100
|
pi-review --mode challenge --continue <sessionHandle> -- "expand finding 2"
|
|
69
101
|
```
|
|
102
|
+
|
|
103
|
+
## Status examples (user phrasing)
|
|
104
|
+
|
|
105
|
+
- "review status" / "is the review still running?" → run **Review status** checks above.
|
|
106
|
+
- "show streaming in Claude Code" / "the wait is bad, is there a better way" → explain tool buffering; re-run with `--progress-log <path>` + a background/tail workflow (or Pi `/rv`) for live progress, offering a local terminal only as a fallback.
|