@unifan/pi-unifan-zh 1.0.10 → 1.0.12
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/extensions/commit/index.ts +24 -2
- package/extensions/commit/package.json +1 -1
- package/extensions/commit/src/git.ts +33 -2
- package/extensions/review/index.ts +28 -2
- package/extensions/review/src/cli-args.ts +7 -1
- package/extensions/review/src/config.ts +2 -2
- package/extensions/review/src/directive.ts +41 -4
- package/extensions/review/src/review-run.ts +12 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
commitWithMsg,
|
|
4
4
|
getChangedFiles,
|
|
5
5
|
getStagedDiff,
|
|
6
|
+
getUnpushedCommits,
|
|
6
7
|
getUnstagedDiff,
|
|
7
8
|
isGitRepo,
|
|
8
9
|
pushCurrentBranch,
|
|
@@ -114,7 +115,28 @@ export default function (pi: ExtensionAPI) {
|
|
|
114
115
|
const changedFiles = await getChangedFiles(ctx.cwd);
|
|
115
116
|
|
|
116
117
|
if (!stagedDiff && !unstagedDiff) {
|
|
117
|
-
|
|
118
|
+
if (andPush) {
|
|
119
|
+
const unpushed = await getUnpushedCommits(ctx.cwd);
|
|
120
|
+
if (unpushed.length > 0) {
|
|
121
|
+
notify(`当前工作区干净,但检测到有 ${unpushed.length} 个未推送提交,正在推送到远端...`, "info");
|
|
122
|
+
const pushRes = await pushCurrentBranch(ctx.cwd, (msg) => notify(msg, "info"));
|
|
123
|
+
if (pushRes.ok) {
|
|
124
|
+
const successMsg = pushRes.autoRebased
|
|
125
|
+
? `🚀 已自动完成变基 (pull --rebase),并成功将 ${unpushed.length} 个本地提交推送至远端!`
|
|
126
|
+
: `🚀 成功将本地未推送的 ${unpushed.length} 个提交推送至远端!`;
|
|
127
|
+
notify(successMsg, "info");
|
|
128
|
+
pi.sendMessage({
|
|
129
|
+
customType: "pi-commit-result",
|
|
130
|
+
content: `### 🚀 远端推送完成\n\n当前工作区无未提交的修改,已成功将本地 **${unpushed.length} 个历史提交** 推送至远端分支:\n\n\`\`\`text\n${unpushed.join("\n")}\n\`\`\`\n\n${successMsg}`,
|
|
131
|
+
display: true,
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
notify(`⚠️ 推送失败: ${pushRes.output}`, "error");
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
notify("当前工作区干净,且所有本地提交均已同步至远端,无需提交和推送。", "info");
|
|
118
140
|
return;
|
|
119
141
|
}
|
|
120
142
|
|
|
@@ -178,7 +200,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
178
200
|
});
|
|
179
201
|
|
|
180
202
|
pi.registerCommand("commit-push", {
|
|
181
|
-
description: "智能 Git 提交并推流:生成标准中文 Commit 后自动提交并执行 git push (
|
|
203
|
+
description: "智能 Git 提交并推流:生成标准中文 Commit 后自动提交并执行 git push (支持自动变基与未推送提交自动推流)",
|
|
182
204
|
handler: async (args, ctx) => {
|
|
183
205
|
await handleCommitCommand(args, ctx, true);
|
|
184
206
|
},
|
|
@@ -45,6 +45,27 @@ export async function getChangedFiles(cwd: string): Promise<string[]> {
|
|
|
45
45
|
.filter(Boolean);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
export async function getUnpushedCommits(cwd: string): Promise<string[]> {
|
|
49
|
+
const res = await runGit(cwd, ["log", "@{u}..HEAD", "--oneline"]);
|
|
50
|
+
if (res.ok && res.stdout.trim()) {
|
|
51
|
+
return res.stdout.trim().split("\n").filter(Boolean);
|
|
52
|
+
}
|
|
53
|
+
const statusRes = await runGit(cwd, ["status", "--porcelain=v1", "-b"]);
|
|
54
|
+
if (statusRes.ok) {
|
|
55
|
+
const match = statusRes.stdout.match(/\[ahead\s+(\d+)\]/);
|
|
56
|
+
if (match && match[1]) {
|
|
57
|
+
const count = parseInt(match[1], 10);
|
|
58
|
+
if (count > 0) {
|
|
59
|
+
const logRes = await runGit(cwd, ["log", "-n", String(count), "--oneline"]);
|
|
60
|
+
if (logRes.ok && logRes.stdout.trim()) {
|
|
61
|
+
return logRes.stdout.trim().split("\n").filter(Boolean);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
|
|
48
69
|
export async function stageAll(cwd: string): Promise<boolean> {
|
|
49
70
|
const res = await runGit(cwd, ["add", "-A"]);
|
|
50
71
|
return res.ok;
|
|
@@ -59,12 +80,22 @@ export async function pushCurrentBranch(
|
|
|
59
80
|
cwd: string,
|
|
60
81
|
onLog?: (msg: string) => void,
|
|
61
82
|
): Promise<{ ok: boolean; output: string; autoRebased?: boolean }> {
|
|
62
|
-
|
|
83
|
+
let initialPush = await runGit(cwd, ["push"]);
|
|
63
84
|
if (initialPush.ok) {
|
|
64
85
|
return { ok: true, output: initialPush.stdout || "推送成功" };
|
|
65
86
|
}
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
let pushError = `${initialPush.stderr} ${initialPush.stdout}`.toLowerCase();
|
|
89
|
+
|
|
90
|
+
if (pushError.includes("no upstream branch") || pushError.includes("set-upstream")) {
|
|
91
|
+
if (onLog) onLog("未关联远端分支,正在自动关联并推送 (git push -u origin HEAD)...");
|
|
92
|
+
const setUpstream = await runGit(cwd, ["push", "-u", "origin", "HEAD"]);
|
|
93
|
+
if (setUpstream.ok) {
|
|
94
|
+
return { ok: true, output: setUpstream.stdout || "推送成功" };
|
|
95
|
+
}
|
|
96
|
+
pushError = `${setUpstream.stderr} ${setUpstream.stdout}`.toLowerCase();
|
|
97
|
+
}
|
|
98
|
+
|
|
68
99
|
const needsPull =
|
|
69
100
|
pushError.includes("fetch first") ||
|
|
70
101
|
pushError.includes("non-fast-forward") ||
|
|
@@ -25,11 +25,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
25
25
|
registerReviewReportTool(pi);
|
|
26
26
|
registerPiReviewRenderer(pi);
|
|
27
27
|
pi.registerCommand("review", {
|
|
28
|
-
description: "
|
|
28
|
+
description: "启动日常 AI 代码审查 (3 大核心专家 + 门禁裁判长)。--lite 极速单兵,--perf 性能审查,--full 全量会诊。",
|
|
29
29
|
getArgumentCompletions: (prefix: string) => {
|
|
30
30
|
const options = [
|
|
31
|
-
{ value: "--lite", label: "--lite", description: "极速单专家审查 (
|
|
31
|
+
{ value: "--lite", label: "--lite", description: "极速单专家审查 (无门禁,低延迟极省 Token)" },
|
|
32
32
|
{ value: "--perf", label: "--perf", description: "专项性能与基准测试审查 (GC/内存分配/CPU/Benchmark)" },
|
|
33
|
+
{ value: "--full", label: "--full", description: "全量 6 专家深度会诊 (Bugbot/安全/合规/历史/注释/性能 + 门禁)" },
|
|
33
34
|
{ value: "--gate-model", label: "--gate-model", description: "指定当前审查的门禁裁判模型" },
|
|
34
35
|
];
|
|
35
36
|
const trimmed = prefix.trimStart();
|
|
@@ -69,6 +70,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
69
70
|
input: parsed.input,
|
|
70
71
|
lite: parsed.lite,
|
|
71
72
|
perf: parsed.perf,
|
|
73
|
+
full: parsed.full,
|
|
72
74
|
gateModel: parsed.gateModel,
|
|
73
75
|
});
|
|
74
76
|
if (!prepared) {
|
|
@@ -139,6 +141,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
139
141
|
},
|
|
140
142
|
});
|
|
141
143
|
|
|
144
|
+
pi.registerCommand("review-full", {
|
|
145
|
+
description: "全量 6 专家深度代码审查会诊 (Bugbot/安全/合规/历史/注释/性能 + 门禁总裁判)",
|
|
146
|
+
handler: async (args, ctx) => {
|
|
147
|
+
const notify = (msg: string, level: "info" | "warning" | "error" = "info") => {
|
|
148
|
+
if (ctx.hasUI) ctx.ui.notify(msg, level);
|
|
149
|
+
else console.log(`pi-review: ${msg}`);
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
const { config, legacyWarnings } = loadConfig();
|
|
153
|
+
for (const w of legacyWarnings) notify(`pi-review 提示: ${w}`, "warning");
|
|
154
|
+
pi.sendMessage({ customType: "pi-review", content: args ? `/review-full ${args}` : "/review-full", display: true });
|
|
155
|
+
const prepared = await prepareRun({ cwd: ctx.cwd, input: args, full: true });
|
|
156
|
+
if (!prepared) {
|
|
157
|
+
notify("没有检测到需要审查的内容 (未找到修改、PR 或非 Git 仓库)。", "info");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
pi.sendMessage({ customType: "pi-review-directive", content: prepared.directiveText, display: false }, { triggerTurn: true });
|
|
161
|
+
} catch (err) {
|
|
162
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
163
|
+
notify(`全量代码审查启动失败: ${message}`, "error");
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
142
168
|
pi.registerCommand("review-config", {
|
|
143
169
|
description: "编辑代码审查配置 (~/.pi/agent/pi-review.json)",
|
|
144
170
|
handler: async (_args, ctx) => {
|
|
@@ -11,6 +11,8 @@ export interface ParsedReviewArgs {
|
|
|
11
11
|
lite: boolean;
|
|
12
12
|
/** Dedicated performance review mode: perf-reviewer with benchmark capabilities. */
|
|
13
13
|
perf: boolean;
|
|
14
|
+
/** Full multi-agent review mode: all 6 reviewers + gate. */
|
|
15
|
+
full: boolean;
|
|
14
16
|
/** Override the gate model for this run (otherwise config.gate.model). */
|
|
15
17
|
gateModel?: string;
|
|
16
18
|
}
|
|
@@ -24,7 +26,7 @@ const LEGACY_VALUED_FLAGS = new Set([
|
|
|
24
26
|
|
|
25
27
|
export function parseReviewArgs(raw: string): ParsedReviewArgs {
|
|
26
28
|
const tokens = tokenize(raw);
|
|
27
|
-
const result: ParsedReviewArgs = { noSpawn: false, lite: false, perf: false };
|
|
29
|
+
const result: ParsedReviewArgs = { noSpawn: false, lite: false, perf: false, full: false };
|
|
28
30
|
const inputParts: string[] = [];
|
|
29
31
|
|
|
30
32
|
for (let i = 0; i < tokens.length; i++) {
|
|
@@ -45,6 +47,10 @@ export function parseReviewArgs(raw: string): ParsedReviewArgs {
|
|
|
45
47
|
result.perf = true;
|
|
46
48
|
continue;
|
|
47
49
|
}
|
|
50
|
+
if (t === "--full") {
|
|
51
|
+
result.full = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
48
54
|
if (t === "--gate-model") {
|
|
49
55
|
const id = tokens[++i];
|
|
50
56
|
if (id) result.gateModel = id;
|
|
@@ -55,7 +55,7 @@ export const DEFAULT_CONFIG: PiReviewConfig = {
|
|
|
55
55
|
"history-context": {
|
|
56
56
|
id: "history-context",
|
|
57
57
|
label: "History Context",
|
|
58
|
-
enabled:
|
|
58
|
+
enabled: false,
|
|
59
59
|
model: "inherit",
|
|
60
60
|
},
|
|
61
61
|
"security-review": {
|
|
@@ -67,7 +67,7 @@ export const DEFAULT_CONFIG: PiReviewConfig = {
|
|
|
67
67
|
"code-comments": {
|
|
68
68
|
id: "code-comments",
|
|
69
69
|
label: "Code Comments",
|
|
70
|
-
enabled:
|
|
70
|
+
enabled: false,
|
|
71
71
|
model: "inherit",
|
|
72
72
|
},
|
|
73
73
|
conventions: {
|
|
@@ -237,9 +237,9 @@ export function buildWorkflowScript(input: {
|
|
|
237
237
|
const READ_ONLY_PREFIX =
|
|
238
238
|
"只读任务(READ-ONLY)——仅执行审查分析与基准测试。严禁修改源码文件。仅返回审查发现。所有分析总结、问题描述与建议必须使用纯正中文。";
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
lines.push("");
|
|
242
|
-
lines.push("
|
|
240
|
+
lines.push("let reviewers;");
|
|
241
|
+
lines.push("try {");
|
|
242
|
+
lines.push(" reviewers = await runs.all([");
|
|
243
243
|
for (const r of reviewers) {
|
|
244
244
|
const tb = LEAN_BUDGETS.defaultToolBudget;
|
|
245
245
|
const tbForId = r.id === "history-context" ? LEAN_BUDGETS.historyToolBudget : tb;
|
|
@@ -303,7 +303,44 @@ export function buildWorkflowScript(input: {
|
|
|
303
303
|
);
|
|
304
304
|
lines.push(" },");
|
|
305
305
|
}
|
|
306
|
-
lines.push("]);");
|
|
306
|
+
lines.push(" ]);");
|
|
307
|
+
lines.push("} catch (firstErr) {");
|
|
308
|
+
lines.push(" // 自动网络重试保护:若首次并发因网络波动超时,自动延迟800ms后重试一次");
|
|
309
|
+
lines.push(" await new Promise((resolve) => setTimeout(resolve, 800));");
|
|
310
|
+
lines.push(" reviewers = await runs.all([");
|
|
311
|
+
for (const r of reviewers) {
|
|
312
|
+
const tb = LEAN_BUDGETS.defaultToolBudget;
|
|
313
|
+
const tbForId = r.id === "history-context" ? LEAN_BUDGETS.historyToolBudget : tb;
|
|
314
|
+
const taskParts = [
|
|
315
|
+
READ_ONLY_PREFIX,
|
|
316
|
+
`读取 ${JSON.stringify(diffPath)} 作为改动内容——diff 是权威的修改记录,工作区文件仅作上下文参考。所有问题描述必须使用中文。`,
|
|
317
|
+
`你的当前工作区为目标工作区 (${JSON.stringify(workspacePath)})。在此目录下执行必要的 read/grep。`,
|
|
318
|
+
"在额度内完成分析;最终回复必须输出格式规范的 Markdown 审查报告(包含中文 Summary / Findings / Coverage 章节)并停止。所有问题描述、证据引用和总结必须使用纯正中文。",
|
|
319
|
+
];
|
|
320
|
+
const modelClause =
|
|
321
|
+
r.model && r.model !== "inherit"
|
|
322
|
+
? `\n model: ${JSON.stringify(r.model)},`
|
|
323
|
+
: "";
|
|
324
|
+
lines.push(" {");
|
|
325
|
+
lines.push(` key: ${JSON.stringify(r.id)},`);
|
|
326
|
+
lines.push(` agent: ${JSON.stringify(leanAgentName(r.id))},`);
|
|
327
|
+
lines.push(` task: [`);
|
|
328
|
+
for (const part of taskParts) {
|
|
329
|
+
lines.push(` ${JSON.stringify(part)},`);
|
|
330
|
+
}
|
|
331
|
+
lines.push(` ].join(" "),`);
|
|
332
|
+
lines.push(` cwd: ${JSON.stringify(workspacePath)},`);
|
|
333
|
+
if (r.thinking) {
|
|
334
|
+
lines.push(` thinking: ${JSON.stringify(r.thinking)},`);
|
|
335
|
+
}
|
|
336
|
+
lines.push(` toolBudget: { soft: ${tbForId.soft}, hard: ${tbForId.hard} },`);
|
|
337
|
+
lines.push(
|
|
338
|
+
` turnBudget: { maxTurns: ${budgets.turnBudget.maxTurns}, graceTurns: ${budgets.turnBudget.graceTurns} },${modelClause}`,
|
|
339
|
+
);
|
|
340
|
+
lines.push(" },");
|
|
341
|
+
}
|
|
342
|
+
lines.push(" ]);");
|
|
343
|
+
lines.push("}");
|
|
307
344
|
lines.push("");
|
|
308
345
|
|
|
309
346
|
// Gate
|
|
@@ -49,6 +49,8 @@ export interface PrepareRunInput {
|
|
|
49
49
|
lite?: boolean;
|
|
50
50
|
/** Support `--perf` performance & benchmark mode. */
|
|
51
51
|
perf?: boolean;
|
|
52
|
+
/** Support `--full` all 6 reviewers mode. */
|
|
53
|
+
full?: boolean;
|
|
52
54
|
/** Optional per-run gate model override. */
|
|
53
55
|
gateModel?: string;
|
|
54
56
|
/** Set false for dry-runs — pruning is a side effect a dry run must not have. */
|
|
@@ -150,7 +152,16 @@ export async function prepareRun(input: PrepareRunInput): Promise<PreparedRun |
|
|
|
150
152
|
? [{ id: "perf-review", label: "Performance Review", enabled: true, model: "inherit" }]
|
|
151
153
|
: input.lite
|
|
152
154
|
? [{ id: "lite-review", label: "Lite Review", enabled: true, model: "inherit" }]
|
|
153
|
-
:
|
|
155
|
+
: input.full
|
|
156
|
+
? [
|
|
157
|
+
{ id: "claude-md-compliance", label: "Claude-MD Compliance", enabled: true, model: "inherit" },
|
|
158
|
+
{ id: "bugbot", label: "Bugbot", enabled: true, model: "inherit" },
|
|
159
|
+
{ id: "security-review", label: "Security Review", enabled: true, model: "inherit" },
|
|
160
|
+
{ id: "history-context", label: "History Context", enabled: true, model: "inherit" },
|
|
161
|
+
{ id: "code-comments", label: "Code Comments", enabled: true, model: "inherit" },
|
|
162
|
+
{ id: "perf-review", label: "Performance Review", enabled: true, model: "inherit" },
|
|
163
|
+
]
|
|
164
|
+
: reviewersForRouting(target, config, profile);
|
|
154
165
|
const skippedReasons = adaptiveSkips(profile);
|
|
155
166
|
// The report tool uses this to reject findings that did not come from this
|
|
156
167
|
// run's roster (stale-artifact contamination guard).
|