@zephyrdeng/pi-review 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -4
- package/README.zh-CN.md +57 -2
- package/dist/child-isolation.test.js +25 -7
- package/dist/child-isolation.test.js.map +1 -1
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/extensions/review.d.ts +2 -0
- package/dist/extensions/review.js +22 -0
- package/dist/extensions/rv-prompts.d.ts +19 -0
- package/dist/extensions/rv-prompts.js +132 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/panel-cli.test.js +35 -0
- package/dist/panel-cli.test.js.map +1 -1
- package/dist/panel-config.d.ts +7 -0
- package/dist/panel-config.d.ts.map +1 -1
- package/dist/panel-config.js +12 -0
- package/dist/panel-config.js.map +1 -1
- package/dist/panel.d.ts +7 -1
- package/dist/panel.d.ts.map +1 -1
- package/dist/panel.js +44 -1
- package/dist/panel.js.map +1 -1
- package/dist/pi-config.d.ts +54 -0
- package/dist/pi-config.d.ts.map +1 -0
- package/dist/pi-config.js +87 -0
- package/dist/pi-config.js.map +1 -0
- package/dist/pi-config.test.d.ts +2 -0
- package/dist/pi-config.test.d.ts.map +1 -0
- package/dist/pi-config.test.js +78 -0
- package/dist/pi-config.test.js.map +1 -0
- package/dist/provider-probe.test.d.ts +2 -0
- package/dist/provider-probe.test.d.ts.map +1 -0
- package/dist/provider-probe.test.js +192 -0
- package/dist/provider-probe.test.js.map +1 -0
- package/dist/review.d.ts +58 -5
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +159 -9
- package/dist/review.js.map +1 -1
- package/dist/skill-protocol.test.js +13 -0
- package/dist/skill-protocol.test.js.map +1 -1
- package/dist/types.d.ts +25 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/extensions/review.test.ts +8 -0
- package/extensions/review.ts +11 -0
- package/extensions/rv-config.test.ts +90 -0
- package/extensions/rv-config.ts +150 -0
- package/extensions/rv-prompts.ts +1 -1
- package/package.json +1 -1
- package/resources/providers/orcarouter.json +28 -0
- package/skills/pi-review/SKILL.md +14 -1
- package/skills/pi-review/references/codex-tools.md +2 -0
- package/skills/pi-review/references/model-selection.md +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { test } from "vitest";
|
|
6
|
+
import { configFilePath, loadReviewConfigFile, parseReviewConfig, resolveChildExtensions, } from "./pi-config.js";
|
|
7
|
+
test("parseReviewConfig accepts an empty or full valid config", () => {
|
|
8
|
+
assert.deepEqual(parseReviewConfig("{}"), { config: {}, warnings: [] });
|
|
9
|
+
assert.deepEqual(parseReviewConfig('{"childExtensions": true}'), { config: { childExtensions: true }, warnings: [] });
|
|
10
|
+
assert.deepEqual(parseReviewConfig('{"childExtensions": false}'), { config: { childExtensions: false }, warnings: [] });
|
|
11
|
+
});
|
|
12
|
+
test("parseReviewConfig ignores unknown keys for forward compatibility", () => {
|
|
13
|
+
assert.deepEqual(parseReviewConfig('{"childExtensions": true, "futureKey": 42}'), {
|
|
14
|
+
config: { childExtensions: true },
|
|
15
|
+
warnings: [],
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
test("parseReviewConfig treats null as explicitly unset", () => {
|
|
19
|
+
assert.deepEqual(parseReviewConfig('{"childExtensions": null}'), { config: {}, warnings: [] });
|
|
20
|
+
});
|
|
21
|
+
test("parseReviewConfig leniently ignores a non-boolean value with a warning", () => {
|
|
22
|
+
const load = parseReviewConfig('{"childExtensions": "true"}');
|
|
23
|
+
assert.deepEqual(load.config, {});
|
|
24
|
+
assert.equal(load.warnings.length, 1);
|
|
25
|
+
assert.match(load.warnings[0], /childExtensions/);
|
|
26
|
+
assert.match(load.warnings[0], /ignoring it/);
|
|
27
|
+
const num = parseReviewConfig('{"childExtensions": 1}');
|
|
28
|
+
assert.deepEqual(num.config, {});
|
|
29
|
+
assert.equal(num.warnings.length, 1);
|
|
30
|
+
});
|
|
31
|
+
test("parseReviewConfig leniently handles invalid JSON and non-object roots with a warning", () => {
|
|
32
|
+
const badJson = parseReviewConfig("not json");
|
|
33
|
+
assert.deepEqual(badJson.config, {});
|
|
34
|
+
assert.equal(badJson.warnings.length, 1);
|
|
35
|
+
assert.match(badJson.warnings[0], /not valid JSON/);
|
|
36
|
+
const arr = parseReviewConfig("[1, 2]");
|
|
37
|
+
assert.deepEqual(arr.config, {});
|
|
38
|
+
assert.equal(arr.warnings.length, 1);
|
|
39
|
+
const nul = parseReviewConfig("null");
|
|
40
|
+
assert.deepEqual(nul.config, {});
|
|
41
|
+
assert.equal(nul.warnings.length, 1);
|
|
42
|
+
});
|
|
43
|
+
test("loadReviewConfigFile treats a missing file as the empty config with no warnings", () => {
|
|
44
|
+
const missing = path.join(os.tmpdir(), `pi-review-config-missing-${Date.now()}.json`);
|
|
45
|
+
assert.deepEqual(loadReviewConfigFile(missing), { config: {}, warnings: [] });
|
|
46
|
+
});
|
|
47
|
+
test("loadReviewConfigFile loads and validates an existing file", () => {
|
|
48
|
+
const file = path.join(os.tmpdir(), `pi-review-config-${Date.now()}.json`);
|
|
49
|
+
fs.writeFileSync(file, '{"childExtensions": true}');
|
|
50
|
+
try {
|
|
51
|
+
assert.deepEqual(loadReviewConfigFile(file), { config: { childExtensions: true }, warnings: [] });
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
fs.rmSync(file, { force: true });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
test("configFilePath defaults under the pi data dir and honors PI_REVIEW_CONFIG", () => {
|
|
58
|
+
assert.equal(configFilePath({}), path.join(os.homedir(), ".pi", "pi-review", "config.json"));
|
|
59
|
+
assert.equal(configFilePath({ PI_REVIEW_CONFIG: "/tmp/custom.json" }), "/tmp/custom.json");
|
|
60
|
+
});
|
|
61
|
+
test("resolveChildExtensions: env truthy values override config and default", () => {
|
|
62
|
+
for (const value of ["1", "true", "KEEP", " 1 "]) {
|
|
63
|
+
assert.deepEqual(resolveChildExtensions({ PI_REVIEW_CHILD_EXTENSIONS: value }, {}), { enabled: true, source: "env" });
|
|
64
|
+
assert.deepEqual(resolveChildExtensions({ PI_REVIEW_CHILD_EXTENSIONS: value }, { childExtensions: false }), { enabled: true, source: "env" });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
test("resolveChildExtensions: env set to a non-truthy value forces isolation", () => {
|
|
68
|
+
assert.deepEqual(resolveChildExtensions({ PI_REVIEW_CHILD_EXTENSIONS: "0" }, { childExtensions: true }), { enabled: false, source: "env" });
|
|
69
|
+
assert.deepEqual(resolveChildExtensions({ PI_REVIEW_CHILD_EXTENSIONS: "no" }, { childExtensions: true }), { enabled: false, source: "env" });
|
|
70
|
+
});
|
|
71
|
+
test("resolveChildExtensions: unset or empty env falls through to config, then default", () => {
|
|
72
|
+
assert.deepEqual(resolveChildExtensions({}, { childExtensions: true }), { enabled: true, source: "config" });
|
|
73
|
+
assert.deepEqual(resolveChildExtensions({}, { childExtensions: false }), { enabled: false, source: "config" });
|
|
74
|
+
assert.deepEqual(resolveChildExtensions({ PI_REVIEW_CHILD_EXTENSIONS: "" }, { childExtensions: true }), { enabled: true, source: "config" });
|
|
75
|
+
assert.deepEqual(resolveChildExtensions({ PI_REVIEW_CHILD_EXTENSIONS: " " }, { childExtensions: true }), { enabled: true, source: "config" });
|
|
76
|
+
assert.deepEqual(resolveChildExtensions({}, {}), { enabled: false, source: "default" });
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=pi-config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi-config.test.js","sourceRoot":"","sources":["../src/pi-config.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-probe.test.d.ts","sourceRoot":"","sources":["../src/provider-probe.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { afterEach, beforeEach, expect, test, vi } from "vitest";
|
|
3
|
+
vi.mock("node:child_process", () => ({
|
|
4
|
+
spawnSync: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { splitModelProvider } from "./panel-config.js";
|
|
8
|
+
import { collectPanelExtensionHints } from "./panel.js";
|
|
9
|
+
import { configBlockForProvider, extensionOnlyHintText, parseModelCatalog, probeProviderAvailability, unknownProviderHintText, } from "./review.js";
|
|
10
|
+
const CATALOG_WITHOUT_EXT = `provider model context max-out thinking images
|
|
11
|
+
Ai98Pro grok-4.5 500K 128K yes yes
|
|
12
|
+
DeepSeek deepseek-v4-flash 1M 384K yes no
|
|
13
|
+
openai-codex gpt-5.6-luna 272K 128K yes yes
|
|
14
|
+
`;
|
|
15
|
+
const CATALOG_WITH_EXT = `provider model context max-out thinking images
|
|
16
|
+
Ai98Pro grok-4.5 500K 128K yes yes
|
|
17
|
+
px:anthropic claude-sonnet-5 1M 128K yes yes
|
|
18
|
+
px:openai gpt-5.6-luna 272K 128K yes yes
|
|
19
|
+
`;
|
|
20
|
+
function catalogResult(stdout) {
|
|
21
|
+
return { status: 0, stdout, stderr: "", error: undefined, pid: 0, output: [null, stdout, ""], signal: null };
|
|
22
|
+
}
|
|
23
|
+
const mockedSpawnSync = vi.mocked(spawnSync);
|
|
24
|
+
/** Original env value captured at module load; restored after each test. */
|
|
25
|
+
const ORIGINAL_CHILD_EXTENSIONS = process.env.PI_REVIEW_CHILD_EXTENSIONS;
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
mockedSpawnSync.mockReset();
|
|
28
|
+
mockedSpawnSync.mockImplementation((_cmd, args) => {
|
|
29
|
+
const isolated = Array.isArray(args) && args.includes("--no-extensions");
|
|
30
|
+
return catalogResult(isolated ? CATALOG_WITHOUT_EXT : CATALOG_WITH_EXT);
|
|
31
|
+
});
|
|
32
|
+
// Establish the suite's default state deterministically.
|
|
33
|
+
delete process.env.PI_REVIEW_CHILD_EXTENSIONS;
|
|
34
|
+
});
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
if (ORIGINAL_CHILD_EXTENSIONS === undefined) {
|
|
37
|
+
delete process.env.PI_REVIEW_CHILD_EXTENSIONS;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
process.env.PI_REVIEW_CHILD_EXTENSIONS = ORIGINAL_CHILD_EXTENSIONS;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
test("parseModelCatalog extracts distinct provider names via header offsets", () => {
|
|
44
|
+
assert.deepEqual(parseModelCatalog(CATALOG_WITHOUT_EXT), ["Ai98Pro", "DeepSeek", "openai-codex"]);
|
|
45
|
+
});
|
|
46
|
+
test("parseModelCatalog keeps provider names with symbols inside a column", () => {
|
|
47
|
+
const catalog = `provider model
|
|
48
|
+
Pika(Grok) grok-4.5
|
|
49
|
+
px:anthropic claude-sonnet-5
|
|
50
|
+
`;
|
|
51
|
+
assert.deepEqual(parseModelCatalog(catalog), ["Pika(Grok)", "px:anthropic"]);
|
|
52
|
+
});
|
|
53
|
+
test("parseModelCatalog returns [] for non-table output", () => {
|
|
54
|
+
assert.deepEqual(parseModelCatalog("pi - AI coding assistant\n\nUsage: ..."), []);
|
|
55
|
+
});
|
|
56
|
+
test("probeProviderAvailability resolves available from the isolated catalog", () => {
|
|
57
|
+
assert.deepEqual(probeProviderAvailability("pi-avail", "DeepSeek"), { kind: "available" });
|
|
58
|
+
expect(mockedSpawnSync).toHaveBeenCalledTimes(1); // only the isolated catalog is consulted
|
|
59
|
+
});
|
|
60
|
+
test("probeProviderAvailability resolves extension_only from the with-extensions catalog", () => {
|
|
61
|
+
assert.deepEqual(probeProviderAvailability("pi-ext", "px:anthropic"), {
|
|
62
|
+
kind: "extension_only",
|
|
63
|
+
provider: "px:anthropic",
|
|
64
|
+
});
|
|
65
|
+
expect(mockedSpawnSync).toHaveBeenCalledTimes(2);
|
|
66
|
+
});
|
|
67
|
+
test("probeProviderAvailability resolves unknown when missing everywhere", () => {
|
|
68
|
+
assert.deepEqual(probeProviderAvailability("pi-unknown", "nonexistent"), {
|
|
69
|
+
kind: "unknown",
|
|
70
|
+
provider: "nonexistent",
|
|
71
|
+
});
|
|
72
|
+
expect(mockedSpawnSync).toHaveBeenCalledTimes(2);
|
|
73
|
+
});
|
|
74
|
+
test("probeProviderAvailability reports indeterminate and does not cache failed catalog queries", () => {
|
|
75
|
+
mockedSpawnSync.mockImplementation(() => ({
|
|
76
|
+
status: 1,
|
|
77
|
+
stdout: "",
|
|
78
|
+
stderr: "boom",
|
|
79
|
+
error: undefined,
|
|
80
|
+
pid: 0,
|
|
81
|
+
output: [null, "", "boom"],
|
|
82
|
+
signal: null,
|
|
83
|
+
}));
|
|
84
|
+
const first = probeProviderAvailability("pi-broken", "px:anthropic");
|
|
85
|
+
assert.deepEqual(first, { kind: "indeterminate", provider: "px:anthropic" });
|
|
86
|
+
// Failure is not cached: a later successful query re-runs the probe.
|
|
87
|
+
mockedSpawnSync.mockImplementation((_cmd, args) => {
|
|
88
|
+
const isolated = Array.isArray(args) && args.includes("--no-extensions");
|
|
89
|
+
return catalogResult(isolated ? CATALOG_WITHOUT_EXT : CATALOG_WITH_EXT);
|
|
90
|
+
});
|
|
91
|
+
const second = probeProviderAvailability("pi-broken", "px:anthropic");
|
|
92
|
+
assert.deepEqual(second, { kind: "extension_only", provider: "px:anthropic" });
|
|
93
|
+
});
|
|
94
|
+
test("splitModelProvider extracts the provider prefix from provider/model tokens", () => {
|
|
95
|
+
assert.equal(splitModelProvider("px:openai/agnes-2.0-flash"), "px:openai");
|
|
96
|
+
assert.equal(splitModelProvider("openai-codex/gpt-5.6-sol"), "openai-codex");
|
|
97
|
+
assert.equal(splitModelProvider("anthropic/*"), "anthropic");
|
|
98
|
+
assert.equal(splitModelProvider("gpt-5.6-sol"), undefined);
|
|
99
|
+
assert.equal(splitModelProvider("/leading-slash"), undefined);
|
|
100
|
+
assert.equal(splitModelProvider("trailing/"), undefined);
|
|
101
|
+
});
|
|
102
|
+
test("configBlockForProvider skips probing without an explicit provider", () => {
|
|
103
|
+
assert.equal(configBlockForProvider("pi-skip", undefined), undefined);
|
|
104
|
+
expect(mockedSpawnSync).not.toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
test("configBlockForProvider skips probing when extensions are enabled (PI_REVIEW_CHILD_EXTENSIONS=1)", () => {
|
|
107
|
+
process.env.PI_REVIEW_CHILD_EXTENSIONS = "1";
|
|
108
|
+
assert.equal(configBlockForProvider("pi-skip", "px:anthropic"), undefined);
|
|
109
|
+
expect(mockedSpawnSync).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
test("configBlockForProvider returns undefined when the provider is isolated-available", () => {
|
|
112
|
+
assert.equal(configBlockForProvider("pi-avail2", "DeepSeek"), undefined);
|
|
113
|
+
});
|
|
114
|
+
test("configBlockForProvider resolves the provider from a provider/model token (F1 regression)", () => {
|
|
115
|
+
// No --provider; the provider must come from the model string.
|
|
116
|
+
const block = configBlockForProvider("pi-model-ext", undefined, "px:anthropic/claude-sonnet-5");
|
|
117
|
+
assert.ok(block, "expected a config block");
|
|
118
|
+
assert.equal(block.extensionOnly, true);
|
|
119
|
+
assert.match(block.structured.parseError ?? "", /PI_REVIEW_CHILD_EXTENSIONS=1/);
|
|
120
|
+
});
|
|
121
|
+
test("configBlockForProvider does not block on a failed catalog probe (F2 regression)", () => {
|
|
122
|
+
mockedSpawnSync.mockImplementation(() => ({
|
|
123
|
+
status: 1,
|
|
124
|
+
stdout: "",
|
|
125
|
+
stderr: "boom",
|
|
126
|
+
error: undefined,
|
|
127
|
+
pid: 0,
|
|
128
|
+
output: [null, "", "boom"],
|
|
129
|
+
signal: null,
|
|
130
|
+
}));
|
|
131
|
+
assert.equal(configBlockForProvider("pi-broken-f2", "px:anthropic"), undefined);
|
|
132
|
+
});
|
|
133
|
+
test("configBlockForProvider blocks with the extension hint for extension-only providers", () => {
|
|
134
|
+
const block = configBlockForProvider("pi-block-ext", "px:anthropic");
|
|
135
|
+
assert.ok(block, "expected a config block");
|
|
136
|
+
assert.equal(block.extensionOnly, true);
|
|
137
|
+
assert.equal(block.structured.status, "blocked");
|
|
138
|
+
assert.equal(block.structured.verdict, "blocked");
|
|
139
|
+
assert.equal(block.structured.verdictSource, "config_error");
|
|
140
|
+
assert.equal(block.structured.findings.length, 0);
|
|
141
|
+
assert.equal(block.structured.actionableCount, 0);
|
|
142
|
+
assert.match(block.structured.parseError ?? "", /PI_REVIEW_CHILD_EXTENSIONS=1/);
|
|
143
|
+
});
|
|
144
|
+
test("configBlockForProvider blocks with the unknown-provider message", () => {
|
|
145
|
+
const block = configBlockForProvider("pi-block-unk", "nonexistent");
|
|
146
|
+
assert.ok(block, "expected a config block");
|
|
147
|
+
assert.equal(block.extensionOnly, false);
|
|
148
|
+
assert.match(block.structured.parseError ?? "", /was not found/);
|
|
149
|
+
});
|
|
150
|
+
test("collectPanelExtensionHints returns undefined when no reviewer was blocked", () => {
|
|
151
|
+
const submissions = [
|
|
152
|
+
{ reviewerId: "r1", model: "gpt-5.6-sol", durationMs: 1, result: { status: "clean", verdict: "approve", verdictSource: "parsed", findings: [], actionableCount: 0 } },
|
|
153
|
+
];
|
|
154
|
+
assert.equal(collectPanelExtensionHints(submissions), undefined);
|
|
155
|
+
});
|
|
156
|
+
test("collectPanelExtensionHints keeps every provider, deduplicated (F3 regression)", () => {
|
|
157
|
+
const blocked = (id, provider) => ({
|
|
158
|
+
reviewerId: id,
|
|
159
|
+
model: `${provider}/m`,
|
|
160
|
+
durationMs: 0,
|
|
161
|
+
result: {
|
|
162
|
+
status: "blocked",
|
|
163
|
+
verdict: "blocked",
|
|
164
|
+
verdictSource: "config_error",
|
|
165
|
+
parseError: extensionOnlyHintText(provider),
|
|
166
|
+
findings: [],
|
|
167
|
+
actionableCount: 0,
|
|
168
|
+
},
|
|
169
|
+
extensionHint: { provider, availableViaExtension: true },
|
|
170
|
+
});
|
|
171
|
+
const hints = collectPanelExtensionHints([
|
|
172
|
+
blocked("r1", "px:anthropic"),
|
|
173
|
+
blocked("r2", "px:openai"),
|
|
174
|
+
blocked("r3", "px:anthropic"), // duplicate provider: collapsed
|
|
175
|
+
]);
|
|
176
|
+
assert.deepEqual(hints, [
|
|
177
|
+
{ provider: "px:anthropic", availableViaExtension: true },
|
|
178
|
+
{ provider: "px:openai", availableViaExtension: true },
|
|
179
|
+
]);
|
|
180
|
+
});
|
|
181
|
+
test("extensionOnlyHintText mentions the provider and the env var", () => {
|
|
182
|
+
const text = extensionOnlyHintText("px:anthropic");
|
|
183
|
+
assert.match(text, /px:anthropic/);
|
|
184
|
+
assert.match(text, /PI_REVIEW_CHILD_EXTENSIONS=1/);
|
|
185
|
+
assert.match(text, /no-extensions/);
|
|
186
|
+
});
|
|
187
|
+
test("unknownProviderHintText mentions the provider and check guidance", () => {
|
|
188
|
+
const text = unknownProviderHintText("foo");
|
|
189
|
+
assert.match(text, /foo/);
|
|
190
|
+
assert.match(text, /spelling/);
|
|
191
|
+
});
|
|
192
|
+
//# sourceMappingURL=provider-probe.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-probe.test.js","sourceRoot":"","sources":["../src/provider-probe.test.ts"],"names":[],"mappings":"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"}
|
package/dist/review.d.ts
CHANGED
|
@@ -1,18 +1,71 @@
|
|
|
1
|
-
import type { ParsedArgs, ReviewRunResult } from "./types.js";
|
|
1
|
+
import type { ParsedArgs, ReviewRunResult, StructuredReviewResult } from "./types.js";
|
|
2
2
|
import { type ChildRunResult } from "./child-process.js";
|
|
3
|
+
import type { PiReviewConfig } from "./pi-config.js";
|
|
3
4
|
export declare function readReviewStdin(): string;
|
|
4
5
|
export declare function childEnv(piBin: string): NodeJS.ProcessEnv;
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
+
* Parse `pi --list-models` table output into distinct provider names.
|
|
8
|
+
* Columns are space-padded; offsets are derived from the header row so
|
|
9
|
+
* provider names are stable even if they contain spaces.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseModelCatalog(stdout: string): string[];
|
|
12
|
+
export type ProviderProbe = {
|
|
13
|
+
kind: "available";
|
|
14
|
+
} | {
|
|
15
|
+
kind: "extension_only";
|
|
16
|
+
provider: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "unknown";
|
|
19
|
+
provider: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "indeterminate";
|
|
22
|
+
provider: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Decide whether `provider` is usable by an isolated review child
|
|
26
|
+
* (--no-extensions, issue #8). Missing from the isolated catalog but present
|
|
27
|
+
* with host extensions loaded means the provider is registered by an
|
|
28
|
+
* extension; the caller should surface a fix hint instead of a confusing
|
|
29
|
+
* unknown-provider failure. A failed catalog query is `indeterminate`: the
|
|
30
|
+
* caller must not block a possibly-valid provider on probe trouble.
|
|
31
|
+
*/
|
|
32
|
+
export declare function probeProviderAvailability(piBin: string, provider: string): ProviderProbe;
|
|
33
|
+
/** Human-readable fix hint for a provider that only exists via host extensions. */
|
|
34
|
+
export declare function extensionOnlyHintText(provider: string): string;
|
|
35
|
+
/** Human-readable message for a provider missing from the catalog entirely. */
|
|
36
|
+
export declare function unknownProviderHintText(provider: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* When isolation is active and a provider is requested, block before spawning
|
|
39
|
+
* the child if that provider only exists via host extensions (or is unknown),
|
|
40
|
+
* and return a blocked review with an actionable hint. Returns undefined when
|
|
41
|
+
* the review should proceed.
|
|
42
|
+
*/
|
|
43
|
+
export interface ProviderConfigBlock {
|
|
44
|
+
structured: StructuredReviewResult;
|
|
45
|
+
provider: string;
|
|
46
|
+
extensionOnly: boolean;
|
|
47
|
+
}
|
|
48
|
+
export declare function configBlockForProvider(piBin: string, provider: string | undefined, model?: string): ProviderConfigBlock | undefined;
|
|
49
|
+
/** Isolate review children from host Pi extension side effects.
|
|
7
50
|
*
|
|
8
51
|
* Host extensions (usage HUDs, auto-reload bridges, etc.) may capture extension
|
|
9
52
|
* ctx and touch it after session dispose/reload. That throws Pi's stale-context
|
|
10
53
|
* error and can crash an otherwise successful reviewer child (issue #8).
|
|
11
54
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
55
|
+
* The effective value comes from the persistent review config
|
|
56
|
+
* (childExtensions, ~/.pi/pi-review/config.json), overridden per process by
|
|
57
|
+
* PI_REVIEW_CHILD_EXTENSIONS (1/true/keep enable, any other set value
|
|
58
|
+
* disables). Bare CLI stays isolated (--no-extensions) until the config says
|
|
59
|
+
* otherwise.
|
|
60
|
+
*/
|
|
61
|
+
export declare function childIsolationArgs(env?: NodeJS.ProcessEnv, cfg?: PiReviewConfig): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Build the catalog command args so the models command shows the same catalog
|
|
64
|
+
* an actual review child would see: the effective isolation config applies
|
|
65
|
+
* here too (F2: catalog and review must agree on extension-registered
|
|
66
|
+
* providers).
|
|
14
67
|
*/
|
|
15
|
-
export declare function
|
|
68
|
+
export declare function modelsArgs(args: string[], env?: NodeJS.ProcessEnv, cfg?: PiReviewConfig): string[];
|
|
16
69
|
/** Prefer the larger of two captured stderr buffers; keep a readable stack tail. */
|
|
17
70
|
export declare const CHILD_STDERR_DIAGNOSTIC_LIMIT = 12000;
|
|
18
71
|
export declare function formatChildRuntimeDetail(runtimeError: string, stderr: string, limit?: number): string;
|
package/dist/review.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAc,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAc,eAAe,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAClG,OAAO,EAA2C,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAclG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,wBAAgB,eAAe,IAAI,MAAM,CAOxC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAczD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAc1D;AAkCD,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAQxF;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAS9D;AAED,+EAA+E;AAC/E,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,sBAAsB,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,CAAC,EAAE,MAAM,GACb,mBAAmB,GAAG,SAAS,CAoBjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,GAAG,CAAC,EAAE,cAAc,GAAG,MAAM,EAAE,CAGvG;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,GAAG,CAAC,EAAE,cAAc,GAAG,MAAM,EAAE,CAE/G;AAED,oFAAoF;AACpF,eAAO,MAAM,6BAA6B,QAAS,CAAC;AAEpD,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,SAAgC,GAAG,MAAM,CAK5H;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAG/E;AAED;;gFAEgF;AAChF,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,GAAG,OAAO,CAE/F;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,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,SAAoB,GAAG,OAAO,CAAC,eAAe,CAAC,CAiO/G;AAED,+CAA+C;AAC/C,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,CAGlE"}
|
package/dist/review.js
CHANGED
|
@@ -14,7 +14,9 @@ import { ProgressLogWriter } from "./progress-log.js";
|
|
|
14
14
|
import { makeRunSessionDir, newestJsonl } from "./session.js";
|
|
15
15
|
import { fail, hasPathSeparator, expandMaybeHome, normalizeTools } from "./utils.js";
|
|
16
16
|
import { resolveConfig } from "./config.js";
|
|
17
|
+
import { splitModelProvider } from "./panel-config.js";
|
|
17
18
|
import { formatReviewMetaAscii, formatReviewMetaJsonLine } from "./meta-footer.js";
|
|
19
|
+
import { currentConfig, resolveChildExtensions } from "./pi-config.js";
|
|
18
20
|
export function readReviewStdin() {
|
|
19
21
|
if (process.stdin.isTTY)
|
|
20
22
|
return "";
|
|
@@ -40,20 +42,138 @@ export function childEnv(piBin) {
|
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
|
-
*
|
|
45
|
+
* Parse `pi --list-models` table output into distinct provider names.
|
|
46
|
+
* Columns are space-padded; offsets are derived from the header row so
|
|
47
|
+
* provider names are stable even if they contain spaces.
|
|
48
|
+
*/
|
|
49
|
+
export function parseModelCatalog(stdout) {
|
|
50
|
+
const lines = stdout.split("\n");
|
|
51
|
+
const headerIdx = lines.findIndex((l) => l.includes("provider") && l.includes("model"));
|
|
52
|
+
if (headerIdx === -1)
|
|
53
|
+
return [];
|
|
54
|
+
const header = lines[headerIdx];
|
|
55
|
+
const providerStart = header.indexOf("provider");
|
|
56
|
+
const modelStart = header.indexOf("model");
|
|
57
|
+
const providers = new Set();
|
|
58
|
+
for (const line of lines.slice(headerIdx + 1)) {
|
|
59
|
+
if (!line.trim())
|
|
60
|
+
continue;
|
|
61
|
+
const provider = line.slice(providerStart, modelStart).trim();
|
|
62
|
+
if (provider)
|
|
63
|
+
providers.add(provider);
|
|
64
|
+
}
|
|
65
|
+
return [...providers];
|
|
66
|
+
}
|
|
67
|
+
const MODEL_CATALOG_CACHE = new Map();
|
|
68
|
+
function catalogCacheKey(piBin, isolated) {
|
|
69
|
+
return `${piBin}|${isolated ? "no-ext" : "with-ext"}`;
|
|
70
|
+
}
|
|
71
|
+
function listProviders(piBin, isolated) {
|
|
72
|
+
const key = catalogCacheKey(piBin, isolated);
|
|
73
|
+
const cached = MODEL_CATALOG_CACHE.get(key);
|
|
74
|
+
if (cached)
|
|
75
|
+
return cached;
|
|
76
|
+
const args = ["--list-models", ...(isolated ? ["--no-extensions"] : [])];
|
|
77
|
+
const result = spawnSync(piBin, args, {
|
|
78
|
+
env: childEnv(piBin),
|
|
79
|
+
encoding: "utf8",
|
|
80
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
81
|
+
});
|
|
82
|
+
const query = result.status === 0 && result.stdout
|
|
83
|
+
? { providers: new Set(parseModelCatalog(result.stdout)), ok: true }
|
|
84
|
+
: { providers: new Set(), ok: false };
|
|
85
|
+
// Only cache successful queries: a transient spawn failure (slow startup,
|
|
86
|
+
// config read hiccup) must not poison the probe for the rest of the run.
|
|
87
|
+
if (query.ok)
|
|
88
|
+
MODEL_CATALOG_CACHE.set(key, query);
|
|
89
|
+
return query;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Decide whether `provider` is usable by an isolated review child
|
|
93
|
+
* (--no-extensions, issue #8). Missing from the isolated catalog but present
|
|
94
|
+
* with host extensions loaded means the provider is registered by an
|
|
95
|
+
* extension; the caller should surface a fix hint instead of a confusing
|
|
96
|
+
* unknown-provider failure. A failed catalog query is `indeterminate`: the
|
|
97
|
+
* caller must not block a possibly-valid provider on probe trouble.
|
|
98
|
+
*/
|
|
99
|
+
export function probeProviderAvailability(piBin, provider) {
|
|
100
|
+
const isolated = listProviders(piBin, true);
|
|
101
|
+
if (!isolated.ok)
|
|
102
|
+
return { kind: "indeterminate", provider };
|
|
103
|
+
if (isolated.providers.has(provider))
|
|
104
|
+
return { kind: "available" };
|
|
105
|
+
const withExtensions = listProviders(piBin, false);
|
|
106
|
+
if (!withExtensions.ok)
|
|
107
|
+
return { kind: "indeterminate", provider };
|
|
108
|
+
if (withExtensions.providers.has(provider))
|
|
109
|
+
return { kind: "extension_only", provider };
|
|
110
|
+
return { kind: "unknown", provider };
|
|
111
|
+
}
|
|
112
|
+
/** Human-readable fix hint for a provider that only exists via host extensions. */
|
|
113
|
+
export function extensionOnlyHintText(provider) {
|
|
114
|
+
return [
|
|
115
|
+
`provider "${provider}" is registered by a Pi extension, but review children`,
|
|
116
|
+
`run isolated with --no-extensions (issue #8).`,
|
|
117
|
+
"",
|
|
118
|
+
"Enable extension-registered providers persistently in the review config:",
|
|
119
|
+
` ~/.pi/pi-review/config.json → { "childExtensions": true }`,
|
|
120
|
+
"(or override one run: PI_REVIEW_CHILD_EXTENSIONS=1 pi-review ...)",
|
|
121
|
+
].join("\n");
|
|
122
|
+
}
|
|
123
|
+
/** Human-readable message for a provider missing from the catalog entirely. */
|
|
124
|
+
export function unknownProviderHintText(provider) {
|
|
125
|
+
return [
|
|
126
|
+
`provider "${provider}" was not found in the Pi model catalog (with or`,
|
|
127
|
+
`without host extensions loaded). Check --provider / preset provider spelling.`,
|
|
128
|
+
].join("\n");
|
|
129
|
+
}
|
|
130
|
+
export function configBlockForProvider(piBin, provider, model) {
|
|
131
|
+
const probeProvider = provider ?? (model ? splitModelProvider(model) : undefined);
|
|
132
|
+
if (!probeProvider || childIsolationArgs().length === 0)
|
|
133
|
+
return undefined;
|
|
134
|
+
const probe = probeProviderAvailability(piBin, probeProvider);
|
|
135
|
+
// available → proceed; indeterminate (catalog query failed) → proceed and
|
|
136
|
+
// let the child surface its own accurate error rather than block on probe trouble.
|
|
137
|
+
if (probe.kind === "available" || probe.kind === "indeterminate")
|
|
138
|
+
return undefined;
|
|
139
|
+
const extensionOnly = probe.kind === "extension_only";
|
|
140
|
+
return {
|
|
141
|
+
provider: probeProvider,
|
|
142
|
+
extensionOnly,
|
|
143
|
+
structured: {
|
|
144
|
+
status: "blocked",
|
|
145
|
+
verdict: "blocked",
|
|
146
|
+
verdictSource: "config_error",
|
|
147
|
+
parseError: extensionOnly ? extensionOnlyHintText(probeProvider) : unknownProviderHintText(probeProvider),
|
|
148
|
+
findings: [],
|
|
149
|
+
actionableCount: 0,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** Isolate review children from host Pi extension side effects.
|
|
44
154
|
*
|
|
45
155
|
* Host extensions (usage HUDs, auto-reload bridges, etc.) may capture extension
|
|
46
156
|
* ctx and touch it after session dispose/reload. That throws Pi's stale-context
|
|
47
157
|
* error and can crash an otherwise successful reviewer child (issue #8).
|
|
48
158
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
159
|
+
* The effective value comes from the persistent review config
|
|
160
|
+
* (childExtensions, ~/.pi/pi-review/config.json), overridden per process by
|
|
161
|
+
* PI_REVIEW_CHILD_EXTENSIONS (1/true/keep enable, any other set value
|
|
162
|
+
* disables). Bare CLI stays isolated (--no-extensions) until the config says
|
|
163
|
+
* otherwise.
|
|
51
164
|
*/
|
|
52
|
-
export function childIsolationArgs(env = process.env) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
165
|
+
export function childIsolationArgs(env = process.env, cfg) {
|
|
166
|
+
const decision = resolveChildExtensions(env, cfg ?? currentConfig(env).config);
|
|
167
|
+
return decision.enabled ? [] : ["--no-extensions"];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Build the catalog command args so the models command shows the same catalog
|
|
171
|
+
* an actual review child would see: the effective isolation config applies
|
|
172
|
+
* here too (F2: catalog and review must agree on extension-registered
|
|
173
|
+
* providers).
|
|
174
|
+
*/
|
|
175
|
+
export function modelsArgs(args, env = process.env, cfg) {
|
|
176
|
+
return ["--list-models", ...childIsolationArgs(env, cfg), ...args];
|
|
57
177
|
}
|
|
58
178
|
/** Prefer the larger of two captured stderr buffers; keep a readable stack tail. */
|
|
59
179
|
export const CHILD_STDERR_DIAGNOSTIC_LIMIT = 12_000;
|
|
@@ -86,7 +206,7 @@ export function childRuntimeError(child) {
|
|
|
86
206
|
return undefined;
|
|
87
207
|
}
|
|
88
208
|
export function runModels(piBin, args) {
|
|
89
|
-
const result = spawnSync(piBin,
|
|
209
|
+
const result = spawnSync(piBin, modelsArgs(args), {
|
|
90
210
|
stdio: "inherit",
|
|
91
211
|
env: childEnv(piBin),
|
|
92
212
|
});
|
|
@@ -138,6 +258,36 @@ export async function runReviewOnce(parsed, stdinText = readReviewStdin()) {
|
|
|
138
258
|
}
|
|
139
259
|
// Isolate single-review children from host extension side effects (issue #8).
|
|
140
260
|
args.push(...childIsolationArgs());
|
|
261
|
+
// Block before spawning when the requested provider only exists via host
|
|
262
|
+
// extensions (issue #8): the isolated child would otherwise die with a
|
|
263
|
+
// confusing unknown-provider error. Surface an actionable hint so the host
|
|
264
|
+
// agent knows to re-run with PI_REVIEW_CHILD_EXTENSIONS=1.
|
|
265
|
+
const configBlock = configBlockForProvider(config.piBin, parsed.provider || preset.provider, parsed.model || preset.model);
|
|
266
|
+
if (configBlock) {
|
|
267
|
+
const hint = configBlock.structured.parseError ?? "";
|
|
268
|
+
const meta = {
|
|
269
|
+
metaVersion: REVIEW_META_VERSION,
|
|
270
|
+
reviewMode: parsed.mode,
|
|
271
|
+
...configBlock.structured,
|
|
272
|
+
durationMs: 0,
|
|
273
|
+
model: parsed.model || preset.model || null,
|
|
274
|
+
...(parsed.thinking || preset.thinking ? { thinking: parsed.thinking || preset.thinking } : {}),
|
|
275
|
+
...(configBlock.extensionOnly
|
|
276
|
+
? { extensionHint: { provider: configBlock.provider, availableViaExtension: true } }
|
|
277
|
+
: {}),
|
|
278
|
+
};
|
|
279
|
+
process.stdout.write(`${hint}\n\n`);
|
|
280
|
+
process.stdout.write(`${formatReviewMetaAscii(meta)}\n`);
|
|
281
|
+
const metaJsonDest = process.env.PI_REVIEW_META_STDOUT?.toLowerCase();
|
|
282
|
+
const jsonLine = formatReviewMetaJsonLine(meta);
|
|
283
|
+
if (metaJsonDest === "1" || metaJsonDest === "true" || metaJsonDest === "stdout") {
|
|
284
|
+
process.stdout.write(jsonLine);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
process.stderr.write(jsonLine);
|
|
288
|
+
}
|
|
289
|
+
return { meta, exitCode: reviewExitCode(meta.status) };
|
|
290
|
+
}
|
|
141
291
|
args.push(...(payload.attachableFileRefs ?? payload.fileRefs), prompt);
|
|
142
292
|
let progressWriter;
|
|
143
293
|
if (parsed.progressLog) {
|