@fyeeme/pi-review 1.1.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -146
- package/agents/cleaner-altitude.md +18 -0
- package/agents/cleaner-efficiency.md +19 -0
- package/agents/cleaner-reuse.md +16 -0
- package/agents/cleaner-simplification.md +16 -0
- package/agents/finder-conventions.md +23 -0
- package/agents/finder-cross-file.md +21 -0
- package/agents/finder-diff-scan.md +23 -0
- package/agents/finder-language-pitfall.md +21 -0
- package/agents/finder-removed-behavior.md +21 -0
- package/agents/finder-wrapper-proxy.md +23 -0
- package/agents/gap-hunter.md +24 -0
- package/agents/verifier.md +33 -0
- package/index.ts +34 -39
- package/package.json +18 -17
- package/prompts/review.md +23 -0
- package/prompts/simplify.parallel.md +45 -0
- package/prompts/simplify.single.md +23 -0
- package/skills/{code-review → review}/SKILL.md +113 -33
- package/skills/simplify/SKILL.md +56 -44
- package/src/config.ts +103 -0
- package/src/diff.ts +306 -0
- package/src/dispatch.ts +238 -0
- package/src/strategy.ts +76 -0
- package/src/tools/review_report.ts +17 -15
- package/src/commands/code-review.ts +0 -100
- package/src/commands/code-simplify.ts +0 -807
- package/src/concurrency.ts +0 -23
- package/src/tools/subagent.ts +0 -372
package/src/strategy.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/strategy.ts — declarative parallel-strategy evaluation.
|
|
3
|
+
*
|
|
4
|
+
* v1's decideSimplifyMode hardcoded its thresholds in the command handler;
|
|
5
|
+
* v2 declares them as data in the prompt templates' frontmatter and this
|
|
6
|
+
* module evaluates the declared guards against runtime variables. Same
|
|
7
|
+
* semantics, different authority: editing a template's `parallel-when`
|
|
8
|
+
* block changes the strategy with no code change.
|
|
9
|
+
*
|
|
10
|
+
* Fallback-when-unguarded rules (v1 semantics, kept in code because they
|
|
11
|
+
* are safety invariants, not tuning knobs): unmeasurable context and an
|
|
12
|
+
* unavailable fan-out tool always select single-pass.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Guards declared in a template's `parallel-when` frontmatter block. */
|
|
16
|
+
export interface ParallelGuards {
|
|
17
|
+
/** Select single-pass when context usage is at or above this fraction. */
|
|
18
|
+
readonly contextBelow?: number;
|
|
19
|
+
/** Select single-pass when the diff reaches this many chars. */
|
|
20
|
+
readonly diffCharsBelow?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Runtime variables gathered by the dispatcher. */
|
|
24
|
+
export interface StrategyRuntime {
|
|
25
|
+
readonly tokens: number | null;
|
|
26
|
+
readonly contextWindow: number;
|
|
27
|
+
readonly diffChars: number;
|
|
28
|
+
/** Whether fan-out tools are registered in this process (the recursion
|
|
29
|
+
* guard): single-pass is the only option when false. */
|
|
30
|
+
readonly fanoutAvailable: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ReviewVariant = "parallel" | "single-pass";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Evaluate the declared guards. Returns the variant plus the reasons that
|
|
37
|
+
* produced it (rendered into the trigger message so the decision stays
|
|
38
|
+
* observable). Pure — unit-testable.
|
|
39
|
+
*/
|
|
40
|
+
export function selectVariant(
|
|
41
|
+
guards: ParallelGuards,
|
|
42
|
+
runtime: StrategyRuntime,
|
|
43
|
+
): { variant: ReviewVariant; reasons: string[] } {
|
|
44
|
+
const { tokens, contextWindow, diffChars, fanoutAvailable } = runtime;
|
|
45
|
+
const reasons: string[] = [];
|
|
46
|
+
// Conservative safety invariant (not a tunable): if we can't measure
|
|
47
|
+
// context (tokens unknown / window 0), don't risk fan-out.
|
|
48
|
+
if (tokens == null || contextWindow <= 0) reasons.push("context usage unknown");
|
|
49
|
+
if (
|
|
50
|
+
guards.contextBelow != null &&
|
|
51
|
+
tokens != null &&
|
|
52
|
+
contextWindow > 0 &&
|
|
53
|
+
tokens / contextWindow >= guards.contextBelow
|
|
54
|
+
)
|
|
55
|
+
reasons.push(`context ${Math.round((tokens / contextWindow) * 100)}% full`);
|
|
56
|
+
if (guards.diffCharsBelow != null && diffChars >= guards.diffCharsBelow)
|
|
57
|
+
reasons.push(`diff too large (${Math.round(diffChars / 1024)} KB ≥ fan-out threshold)`);
|
|
58
|
+
if (!fanoutAvailable) reasons.push("fan-out unavailable in this context (subagent recursion guard)");
|
|
59
|
+
return { variant: reasons.length > 0 ? "single-pass" : "parallel", reasons };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Parse the `parallel-when` block out of a template's frontmatter map.
|
|
63
|
+
* Unknown/garbage fields are dropped (silent — garbage becomes absent). */
|
|
64
|
+
export function parseGuards(frontmatter: Record<string, unknown>): ParallelGuards {
|
|
65
|
+
const raw = frontmatter["parallel-when"];
|
|
66
|
+
if (!raw || typeof raw !== "object") return {};
|
|
67
|
+
const r = raw as Record<string, unknown>;
|
|
68
|
+
const out: { contextBelow?: number; diffCharsBelow?: number } = {};
|
|
69
|
+
if (typeof r["context-below"] === "number" && r["context-below"] > 0 && r["context-below"] <= 1) {
|
|
70
|
+
out.contextBelow = r["context-below"];
|
|
71
|
+
}
|
|
72
|
+
if (typeof r["diff-chars-below"] === "number" && r["diff-chars-below"] > 0) {
|
|
73
|
+
out.diffCharsBelow = r["diff-chars-below"];
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* no host finding-renderer, so this tool does double duty: it renders a tidy
|
|
8
8
|
* Chinese Markdown report (table + details) back to the conversation AND writes
|
|
9
9
|
* a machine-readable JSON (findings + level + outcome) to
|
|
10
|
-
* `<cwd
|
|
10
|
+
* `<cwd>/<CONFIG_DIR_NAME>/review/<id>.json` so CI / --fix / --comment can
|
|
11
|
+
* consume it.
|
|
11
12
|
*
|
|
12
13
|
* `verdict` (CONFIRMED/PLAUSIBLE) and `outcome` (fixed/skipped/no_change_needed)
|
|
13
14
|
* enums follow the CC ReportFindings shape — values verified against the CC
|
|
@@ -16,8 +17,9 @@
|
|
|
16
17
|
* normalizes stray invalid values (drop the finding / coerce to skipped) rather
|
|
17
18
|
* than failing the whole call.
|
|
18
19
|
*/
|
|
19
|
-
import { defineTool, getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
20
|
+
import { defineTool, getMarkdownTheme, CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
|
|
20
21
|
import { Markdown } from "@earendil-works/pi-tui";
|
|
22
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
21
23
|
import { type Static, Type } from "typebox";
|
|
22
24
|
import * as fs from "node:fs";
|
|
23
25
|
import * as path from "node:path";
|
|
@@ -27,25 +29,25 @@ import * as path from "node:path";
|
|
|
27
29
|
// verdict 两值与 outcome 三档在 2.1.223/226/227 三版本中一致。
|
|
28
30
|
|
|
29
31
|
const VERDICT_VALUES = ["CONFIRMED", "PLAUSIBLE"] as const;
|
|
30
|
-
const Verdict =
|
|
32
|
+
const Verdict = StringEnum(VERDICT_VALUES);
|
|
31
33
|
|
|
32
34
|
const OUTCOME_VALUES = ["fixed", "skipped", "no_change_needed"] as const;
|
|
33
35
|
/** CC ReportFindings `outcome` 三档(2.1.227 二进制实证)。fixed-later 再上报时更新。 */
|
|
34
|
-
const Outcome =
|
|
36
|
+
const Outcome = StringEnum(OUTCOME_VALUES);
|
|
35
37
|
|
|
36
38
|
// 供 SKILL-schema 同步测试引用(防漂移:SKILL 流程契约不得与常量脱节)。
|
|
37
39
|
export { OUTCOME_VALUES, VERDICT_VALUES };
|
|
38
40
|
|
|
39
|
-
const Level =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const Level = StringEnum([
|
|
42
|
+
"low",
|
|
43
|
+
"medium",
|
|
44
|
+
"high",
|
|
45
|
+
"xhigh",
|
|
46
|
+
"max",
|
|
45
47
|
// simplify reuses this tool for structured apply-outcome reporting
|
|
46
48
|
// (harden-code-simplify). Not a review effort level — carries no verdict.
|
|
47
|
-
|
|
48
|
-
]);
|
|
49
|
+
"simplify",
|
|
50
|
+
] as const);
|
|
49
51
|
|
|
50
52
|
// --- schema -----------------------------------------------------------------
|
|
51
53
|
|
|
@@ -223,12 +225,12 @@ export const reviewReportTool = defineTool<typeof ReviewReportParams, ReviewRepo
|
|
|
223
225
|
name: "review_report",
|
|
224
226
|
label: "Report review findings",
|
|
225
227
|
description:
|
|
226
|
-
"Report code-review findings as a typed list — Pi's counterpart to CC's ReportFindings. Use this only when the active code-review instructions tell you to report findings with this tool. Call it once with the verified findings ranked most-severe first (empty array if nothing survived verification) and do not also print the findings as text — the tool renders a tidy Chinese Markdown report back to the conversation AND writes a machine-readable JSON to
|
|
228
|
+
"Report code-review findings as a typed list — Pi's counterpart to CC's ReportFindings. Use this only when the active code-review instructions tell you to report findings with this tool. Call it once with the verified findings ranked most-severe first (empty array if nothing survived verification) and do not also print the findings as text — the tool renders a tidy Chinese Markdown report back to the conversation AND writes a machine-readable JSON to the project's pi config dir (CONFIG_DIR_NAME, typically `.pi`) under review/ for CI / --fix / --comment. When re-reporting after applying fixes, set `outcome` on each finding. 上报结构化 code-review 发现(CC ReportFindings 的 Pi 对等物)。",
|
|
227
229
|
promptSnippet: "review_report — report structured code-review findings (renders Markdown + writes JSON for CI)",
|
|
228
230
|
promptGuidelines: [
|
|
229
231
|
"After verify + dedup, call `review_report` once with { level, findings } (most-severe first; empty array if none survived). Do not also hand-write the Markdown table — this tool renders it.",
|
|
230
232
|
"On re-report after --fix, set each finding's `outcome` (fixed / skipped / no_change_needed).",
|
|
231
|
-
"Use
|
|
233
|
+
"Use `review_report` only when the code-review skill instructs reporting findings; otherwise follow the active output format.",
|
|
232
234
|
],
|
|
233
235
|
parameters: ReviewReportParams,
|
|
234
236
|
|
|
@@ -267,7 +269,7 @@ export const reviewReportTool = defineTool<typeof ReviewReportParams, ReviewRepo
|
|
|
267
269
|
let writeError: string | null = null;
|
|
268
270
|
const now = new Date();
|
|
269
271
|
try {
|
|
270
|
-
const dir = path.join(ctx.cwd,
|
|
272
|
+
const dir = path.join(ctx.cwd, CONFIG_DIR_NAME, "review");
|
|
271
273
|
await fs.promises.mkdir(dir, { recursive: true });
|
|
272
274
|
const safeId = toolCallId.replace(/[^\w.-]+/g, "_");
|
|
273
275
|
const ts = now.toISOString().replace(/[:.]/g, "-");
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as os from "node:os";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { bundledSkillPath } from "../skills.ts";
|
|
6
|
-
|
|
7
|
-
/** Effort levels the /code-review command accepts (mirrors CC's effort enum). */
|
|
8
|
-
export const REVIEW_LEVELS = ["low", "medium", "high", "xhigh", "max"] as const;
|
|
9
|
-
export type ReviewLevel = (typeof REVIEW_LEVELS)[number];
|
|
10
|
-
|
|
11
|
-
const DEFAULT_LEVEL: ReviewLevel = "low";
|
|
12
|
-
|
|
13
|
-
/** Where the last explicitly-typed effort is persisted (CC 2.1.223 codeReviewLastEffort). */
|
|
14
|
-
const STATE_FILE = path.join(os.homedir(), ".pi", ".pi-review-state.json");
|
|
15
|
-
|
|
16
|
-
export type EffortSource = "explicit" | "last-used" | "default";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Parse a leading effort level out of raw args; the remainder (flags + target)
|
|
20
|
-
* is returned verbatim. Pure — unit-testable. Mirrors CC's ecl()/Tjn(): the
|
|
21
|
-
* first token is the level only if it matches the enum; otherwise the whole
|
|
22
|
-
* string is the target/flags.
|
|
23
|
-
*/
|
|
24
|
-
export function parseReviewArgs(args: string): { level: ReviewLevel | undefined; rest: string } {
|
|
25
|
-
const tokens = (args ?? "").trim().split(/\s+/).filter(Boolean);
|
|
26
|
-
if (tokens.length === 0) return { level: undefined, rest: "" };
|
|
27
|
-
const first = tokens[0]!.toLowerCase();
|
|
28
|
-
const isLevel = (REVIEW_LEVELS as readonly string[]).includes(first);
|
|
29
|
-
return {
|
|
30
|
-
level: isLevel ? (first as ReviewLevel) : undefined,
|
|
31
|
-
rest: isLevel ? tokens.slice(1).join(" ") : tokens.join(" "),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Resolve the effective effort + where it came from. Pure — unit-testable.
|
|
37
|
-
* Mirrors CC 2.1.223: explicit wins; otherwise reuse the last-typed level;
|
|
38
|
-
* otherwise the default. (220 defaulted straight to low with no memory.)
|
|
39
|
-
*/
|
|
40
|
-
export function resolveEffort(
|
|
41
|
-
explicit: ReviewLevel | undefined,
|
|
42
|
-
lastUsed: ReviewLevel | undefined,
|
|
43
|
-
): { level: ReviewLevel; source: EffortSource } {
|
|
44
|
-
if (explicit) return { level: explicit, source: "explicit" };
|
|
45
|
-
if (lastUsed) return { level: lastUsed, source: "last-used" };
|
|
46
|
-
return { level: DEFAULT_LEVEL, source: "default" };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Best-effort persistence — sticky-effort is a convenience, not a correctness
|
|
50
|
-
// invariant; a read/write failure must not break the review.
|
|
51
|
-
function readLastEffort(): ReviewLevel | undefined {
|
|
52
|
-
try {
|
|
53
|
-
const raw = JSON.parse(fs.readFileSync(STATE_FILE, "utf8")) as { codeReviewLastEffort?: unknown };
|
|
54
|
-
const v = raw.codeReviewLastEffort;
|
|
55
|
-
return typeof v === "string" && (REVIEW_LEVELS as readonly string[]).includes(v)
|
|
56
|
-
? (v as ReviewLevel)
|
|
57
|
-
: undefined;
|
|
58
|
-
} catch {
|
|
59
|
-
return undefined;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function writeLastEffort(level: ReviewLevel): void {
|
|
63
|
-
try {
|
|
64
|
-
fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
|
|
65
|
-
fs.writeFileSync(STATE_FILE, JSON.stringify({ codeReviewLastEffort: level }));
|
|
66
|
-
} catch {
|
|
67
|
-
/* ignore — non-critical */
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Register the /code-review command — triggers the code-review skill.
|
|
73
|
-
*
|
|
74
|
-
* Handler decides the effective effort deterministically (CC 2.1.223): an
|
|
75
|
-
* explicit level is persisted and used; with no level, the last-typed level is
|
|
76
|
-
* reused; with no history, it falls back to low. The decision is announced in
|
|
77
|
-
* the trigger message so it is observable — same pattern as /code-simplify.
|
|
78
|
-
*/
|
|
79
|
-
export function registerCodeReview(pi: ExtensionAPI): void {
|
|
80
|
-
pi.registerCommand("code-review", {
|
|
81
|
-
description:
|
|
82
|
-
"Review the current diff using the code-review skill. Usage: /code-review [low|medium|high|xhigh|max] [--fix] [--comment] [--share] [<pr#>|<branch>|<path>]",
|
|
83
|
-
getArgumentCompletions(prefix) {
|
|
84
|
-
const tokens = ["low", "medium", "high", "xhigh", "max", "--fix", "--comment", "--share"];
|
|
85
|
-
return tokens.filter((t) => t.startsWith(prefix)).map((t) => ({ label: t, value: t }));
|
|
86
|
-
},
|
|
87
|
-
async handler(args) {
|
|
88
|
-
const { level: explicit, rest } = parseReviewArgs(args ?? "");
|
|
89
|
-
// Skip the read when we just wrote it — resolveEffort returns `explicit` unchanged.
|
|
90
|
-
const lastUsed = explicit ? undefined : readLastEffort();
|
|
91
|
-
if (explicit) writeLastEffort(explicit); // CC 2.1.223: remember the explicit level
|
|
92
|
-
const { level, source } = resolveEffort(explicit, lastUsed);
|
|
93
|
-
pi.sendUserMessage(
|
|
94
|
-
`Run a code review now. Effective effort: ${level} (${source})${rest ? `; extra args: ${rest}` : ""}.\n\n` +
|
|
95
|
-
`First load the review skill with the read tool: ${bundledSkillPath("code-review/SKILL.md")}. ` +
|
|
96
|
-
`Then follow it exactly — use the \`subagent\` tool for any fan-out / verify / gap-hunt the skill calls for.`,
|
|
97
|
-
);
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
}
|