@ghyper9023/pi-dev-workflow 0.7.0 → 0.8.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/.version/RELEASE-v0.8.0.md +93 -0
- package/README.md +140 -108
- package/extensions/dev-prompts.ts +117 -930
- package/extensions/git-commands.ts +2 -2
- package/extensions/grill-me-agent.ts +66 -31
- package/extensions/pre-check.ts +190 -0
- package/extensions/review-detect.ts +123 -0
- package/extensions/session-utils.ts +9 -10
- package/extensions/ui-helpers.ts +25 -5
- package/package.json +1 -1
- package/prompts/APPEND_SYSTEM.md +14 -4
- package/tests/test-no-subagents.mjs +149 -31
|
@@ -1,959 +1,146 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dev Prompts Extension
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* high-quality prompt templates (from ai提示词优化.md) and send the assembled
|
|
6
|
-
* prompt directly to the current agent.
|
|
4
|
+
* 四个命令:/dev-feat、/dev-fix、/dev-refactor、/dev-test。
|
|
7
5
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* /dev-chore - Maintenance / automation
|
|
15
|
-
* /dev-perf - Performance optimization
|
|
16
|
-
* /dev-style - Style / format adjustment
|
|
17
|
-
* /dev-security - Security review
|
|
18
|
-
* /dev-explain - Concept explanation
|
|
19
|
-
* /dev-compare - Comparison evaluation
|
|
6
|
+
* 每个命令的流程一致:
|
|
7
|
+
* 1. 命令参数就是任务原文;不带参数时弹一次必填输入框
|
|
8
|
+
* 2. 复用 pre-check 的意图确认循环:代理先复述目标与问题,用户确认后才继续
|
|
9
|
+
* 3. 提示词只补两件事:AI 的身份与职责、未说明时的默认收尾验收标准
|
|
10
|
+
* —— 任务目标来自第 2 步已确认的意图,不在这里重新解释需求
|
|
11
|
+
* 4. 投递给当前代理执行
|
|
20
12
|
*
|
|
21
|
-
*
|
|
22
|
-
* Leave a field empty (Enter) to skip its section.
|
|
23
|
-
* Press Esc to cancel the entire wizard.
|
|
24
|
-
*
|
|
25
|
-
* Review detection: inputs mentioning review/审查 + code/diff are automatically
|
|
26
|
-
* handled by the review-html skill, running in the current agent.
|
|
13
|
+
* 追问完善(/grill)与 PRD(/prd)是独立命令,不由这里触发。
|
|
27
14
|
*/
|
|
28
15
|
|
|
29
|
-
import * as fs from "node:fs";
|
|
30
|
-
import * as path from "node:path";
|
|
31
16
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
32
|
-
import {
|
|
33
|
-
import { detectProjectDefaults,
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
// ── Helpers ──────────────────────────────────────────────────
|
|
37
|
-
|
|
38
|
-
/** Ask a single question with proper wrapping. Returns `undefined` on cancel (Esc), or BACK_MARKER for back. */
|
|
39
|
-
async function ask(
|
|
40
|
-
ctx: ExtensionCommandContext,
|
|
41
|
-
label: string,
|
|
42
|
-
placeholder: string,
|
|
43
|
-
backable = false,
|
|
44
|
-
initialValue = "",
|
|
45
|
-
): Promise<string | undefined> {
|
|
46
|
-
return uiInput(ctx, label, placeholder, false, backable, initialValue);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** 向导中的一次提问;多个字段用 sep 分隔一次填写,减少提问轮次。 */
|
|
50
|
-
interface WizardQuestion {
|
|
51
|
-
label: string;
|
|
52
|
-
placeholder: string;
|
|
53
|
-
/** 绑定的字段集合,答案按 sep 拆分后依序填入。 */
|
|
54
|
-
keys: string[];
|
|
55
|
-
/** 字段分隔符,默认 " / "。 */
|
|
56
|
-
sep?: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** 将一次回答填入字段。单字段问题时保留完整输入(不按分隔符拆分),避免误切。 */
|
|
60
|
-
function assignAnswers(answers: Record<string, string>, q: WizardQuestion, raw: string): void {
|
|
61
|
-
const parts = q.keys.length === 1 ? [raw] : raw.split(q.sep ?? " / ").map((s) => s.trim());
|
|
62
|
-
q.keys.forEach((k, i) => {
|
|
63
|
-
const part = parts[i];
|
|
64
|
-
if (part) answers[k] = part;
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Check if a field value is empty or explicitly "无". */
|
|
69
|
-
function isEmpty(val: string | undefined): boolean {
|
|
70
|
-
return !val || val.trim() === "" || val.trim() === "无";
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Wrap a non-empty value for template insertion. */
|
|
74
|
-
function wrap(val: string | undefined, fallback = "..."): string {
|
|
75
|
-
if (isEmpty(val)) return fallback;
|
|
76
|
-
return val!.trim();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ── Review helper ────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
/** Find the newest HTML review file generated after `afterMs`(忽略先前遗留的旧报告)。 */
|
|
82
|
-
function findNewestReviewHtml(cwd: string, afterMs: number): string {
|
|
83
|
-
const candidates = [
|
|
84
|
-
path.join(cwd, ".pi-dev-output", "pi-review", "html"),
|
|
85
|
-
path.join(cwd, "pi-review"),
|
|
86
|
-
path.join(cwd, ".pi-dev-output", "pi-review"),
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
for (const reviewDir of candidates) {
|
|
90
|
-
try {
|
|
91
|
-
if (fs.existsSync(reviewDir)) {
|
|
92
|
-
const files = fs.readdirSync(reviewDir)
|
|
93
|
-
.filter(f => f.endsWith(".html"))
|
|
94
|
-
.map(f => ({
|
|
95
|
-
name: f,
|
|
96
|
-
mtime: fs.statSync(path.join(reviewDir, f)).mtimeMs,
|
|
97
|
-
}))
|
|
98
|
-
.filter(f => f.mtime > afterMs)
|
|
99
|
-
.sort((a, b) => b.mtime - a.mtime);
|
|
100
|
-
if (files.length > 0) {
|
|
101
|
-
const rel = path.relative(cwd, reviewDir);
|
|
102
|
-
return rel + "/" + files[0].name;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} catch {
|
|
106
|
-
// ignore fs errors
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return "";
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Run a code review in the current agent and report the result. */
|
|
114
|
-
async function runReview(task: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<string | undefined> {
|
|
115
|
-
const startTime = Date.now();
|
|
116
|
-
ctx.ui.notify("🤖 正在运行代码审查,请稍候...", "info");
|
|
117
|
-
|
|
118
|
-
pi.sendUserMessage(`/skill:review-html\n\n${task}`, {
|
|
119
|
-
deliverAs: "followUp",
|
|
120
|
-
expandPromptTemplates: true,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// 以“报告文件生成为完成标志”进行轮询;不使用 waitForIdle,
|
|
124
|
-
// 避免其在 sendUserMessage 触发的新 turn 开始前立即返回,导致误报“已完成”。
|
|
125
|
-
const deadline = Date.now() + 10 * 60_000;
|
|
126
|
-
let filePath = "";
|
|
127
|
-
while (Date.now() < deadline) {
|
|
128
|
-
filePath = findNewestReviewHtml(ctx.cwd, startTime);
|
|
129
|
-
if (filePath) break;
|
|
130
|
-
await new Promise((r) => setTimeout(r, 2_000));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const dur = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
134
|
-
if (filePath) {
|
|
135
|
-
ctx.ui.notify(`📄 审查报告已生成: ${filePath} (${dur}s)`, "success");
|
|
136
|
-
} else {
|
|
137
|
-
ctx.ui.notify(`⚠️ 审查未生成报告 (${dur}s),请查看当前代理的回复或稍后重试`, "warning");
|
|
138
|
-
}
|
|
139
|
-
return filePath;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ── Template Assemblers ──────────────────────────────────────
|
|
143
|
-
|
|
144
|
-
interface FeatFields {
|
|
145
|
-
language: string;
|
|
146
|
-
techStack: string;
|
|
147
|
-
module: string;
|
|
148
|
-
description: string;
|
|
149
|
-
painPoint: string;
|
|
150
|
-
testCmd: string;
|
|
151
|
-
acceptance?: string;
|
|
152
|
-
extra?: string;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function assembleFeatPrompt(f: FeatFields): string {
|
|
156
|
-
const lines: string[] = [];
|
|
157
|
-
lines.push(`[feat] 在 ${wrap(f.module)} 中实现 ${wrap(f.description)}`);
|
|
158
|
-
lines.push("");
|
|
159
|
-
lines.push(`**角色**:你是一个资深 ${wrap(f.language)} 工程师。`);
|
|
160
|
-
if (!isEmpty(f.techStack) || !isEmpty(f.painPoint)) {
|
|
161
|
-
lines.push(
|
|
162
|
-
`**背景**:项目使用 ${wrap(f.techStack)},当前缺少 ${wrap(f.description)}` +
|
|
163
|
-
(isEmpty(f.painPoint) ? "。" : `,用户痛点是 ${f.painPoint!.trim()}。`),
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
lines.push("**任务**:");
|
|
167
|
-
lines.push("1. 先分析代码库结构,给出逐步实施计划(列出要修改/创建的文件、数据库迁移、对现有代码的假设)。");
|
|
168
|
-
lines.push("2. 计划经我确认后再编写代码。");
|
|
169
|
-
if (!isEmpty(f.testCmd)) {
|
|
170
|
-
lines.push(`3. 实现后编写测试用例验证核心逻辑,并运行 ${f.testCmd!.trim()} 确认通过。`);
|
|
171
|
-
} else {
|
|
172
|
-
lines.push("3. 实现后编写测试用例验证核心逻辑。");
|
|
173
|
-
}
|
|
174
|
-
lines.push("**输出**:提供 unified diff 和两句话的变更说明。");
|
|
175
|
-
lines.push("**约束**:禁止顺手重构无关代码;保持所有公共 API 签名兼容;不要为假设性需求添加抽象层。");
|
|
176
|
-
if (!isEmpty(f.testCmd)) {
|
|
177
|
-
lines.push(`**验证**:运行 ${f.testCmd!.trim()} 确保无回归。`);
|
|
178
|
-
}
|
|
179
|
-
appendMeta(lines, f);
|
|
180
|
-
return lines.join("\n");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
interface FixFields {
|
|
184
|
-
filePath: string;
|
|
185
|
-
lineNumber: string;
|
|
186
|
-
bugDesc: string;
|
|
187
|
-
inputDesc: string;
|
|
188
|
-
expected: string;
|
|
189
|
-
actualError: string;
|
|
190
|
-
testCmd: string;
|
|
191
|
-
acceptance?: string;
|
|
192
|
-
extra?: string;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function assembleFixPrompt(f: FixFields): string {
|
|
196
|
-
const lines: string[] = [];
|
|
197
|
-
const loc = isEmpty(f.lineNumber) ? f.filePath.trim() : `${f.filePath.trim()} #L${f.lineNumber!.trim()}`;
|
|
198
|
-
lines.push(`[fix] 修复 ${loc} 中的 ${wrap(f.bugDesc)}`);
|
|
199
|
-
lines.push("");
|
|
200
|
-
lines.push("**背景**:");
|
|
201
|
-
lines.push(`- 输入:${isEmpty(f.inputDesc) ? "见代码上下文" : f.inputDesc!.trim()}`);
|
|
202
|
-
lines.push(`- 预期行为:${wrap(f.expected, "请描述预期结果")}`);
|
|
203
|
-
lines.push(`- 当前错误:${wrap(f.actualError, "请描述当前错误")}`);
|
|
204
|
-
lines.push("**任务**:");
|
|
205
|
-
lines.push("1. 不要仅仅消除报错(Suppress),要解决根本原因。");
|
|
206
|
-
lines.push("2. 先读取相关代码和日志,诊断根因(多步推理,不要先给结论)。");
|
|
207
|
-
lines.push("3. 提供至少一种修复方案,并说明为什么这样做。");
|
|
208
|
-
lines.push("4. 编写测试用例复现该 Bug 并确认修复有效。");
|
|
209
|
-
lines.push("**输出**:提供 diff 和两句话的根因分析。");
|
|
210
|
-
lines.push("**约束**:只修 bug,不做重构;最小化改动;不要假设错误是微不足道的。");
|
|
211
|
-
if (!isEmpty(f.testCmd)) {
|
|
212
|
-
lines.push(`**验证**:运行 ${f.testCmd!.trim()} 确认修复。`);
|
|
213
|
-
}
|
|
214
|
-
appendMeta(lines, f);
|
|
215
|
-
return lines.join("\n");
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
interface DocFields {
|
|
219
|
-
moduleName: string;
|
|
220
|
-
audience: string;
|
|
221
|
-
keyInfo: string;
|
|
222
|
-
language: string;
|
|
223
|
-
existingMaterial: string;
|
|
224
|
-
acceptance?: string;
|
|
225
|
-
extra?: string;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function assembleDocPrompt(f: DocFields): string {
|
|
229
|
-
const lines: string[] = [];
|
|
230
|
-
lines.push(`[doc] 为 ${wrap(f.moduleName)} 撰写一份文档`);
|
|
231
|
-
lines.push("");
|
|
232
|
-
lines.push("**角色**:你是一位技术文档工程师。");
|
|
233
|
-
if (!isEmpty(f.audience) || !isEmpty(f.keyInfo)) {
|
|
234
|
-
lines.push(`**背景**:目标受众是 ${wrap(f.audience)},他们需要了解 ${wrap(f.keyInfo)}。`);
|
|
235
|
-
}
|
|
236
|
-
lines.push("**任务**:");
|
|
237
|
-
lines.push("1. 提取核心要点,按逻辑结构重组(概述 → 快速开始 → 详细说明 → 常见问题)。");
|
|
238
|
-
lines.push(`2. 添加至少 1 个真实可运行的示例(使用 ${wrap(f.language)} 语法高亮)。`);
|
|
239
|
-
lines.push("3. 如存在争议点,列出不同观点并注明\"无共识\"。");
|
|
240
|
-
if (!isEmpty(f.existingMaterial)) {
|
|
241
|
-
lines.push(`**已有材料**:${f.existingMaterial!.trim()}`);
|
|
242
|
-
}
|
|
243
|
-
lines.push("**输出格式**:Markdown 层级标题,必要时插入表格/列表。");
|
|
244
|
-
lines.push("**约束**:避免空洞词汇(如\"细致入微\"\"深入探究\");每段都应有实质信息;保持原意,不添加原文没有的事实。");
|
|
245
|
-
lines.push("**验证**:请先提供大纲,经我确认后再扩展。");
|
|
246
|
-
appendMeta(lines, f);
|
|
247
|
-
return lines.join("\n");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
interface RefactorFields {
|
|
251
|
-
filePath: string;
|
|
252
|
-
lineCount: string;
|
|
253
|
-
problems: string;
|
|
254
|
-
goal: string;
|
|
255
|
-
testCmd: string;
|
|
256
|
-
acceptance?: string;
|
|
257
|
-
extra?: string;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function assembleRefactorPrompt(f: RefactorFields): string {
|
|
261
|
-
const lines: string[] = [];
|
|
262
|
-
const loc = isEmpty(f.lineCount) ? f.filePath.trim() : `${f.filePath.trim()}(约 ${f.lineCount!.trim()} 行)`;
|
|
263
|
-
lines.push(`[refactor] 对 ${loc} 进行重构,提升 ${wrap(f.goal, "可读性 / 可维护性")}`);
|
|
264
|
-
lines.push("");
|
|
265
|
-
lines.push(`**背景**:当前代码存在 ${wrap(f.problems)}。`);
|
|
266
|
-
lines.push("**任务**:");
|
|
267
|
-
lines.push("1. 识别主要问题。");
|
|
268
|
-
lines.push("2. 提出重构方案,说明改动前后差异。");
|
|
269
|
-
lines.push("3. 输出重构后的完整版本。");
|
|
270
|
-
lines.push("**硬性约束**:");
|
|
271
|
-
lines.push("- 不改变任何行为,保留所有公共 API 签名不变。");
|
|
272
|
-
lines.push("- 禁止顺手优化、禁止添加新功能、禁止修改业务逻辑。");
|
|
273
|
-
if (!isEmpty(f.testCmd)) {
|
|
274
|
-
lines.push(`- 拆分后运行 ${f.testCmd!.trim()} 确认无回归。`);
|
|
275
|
-
}
|
|
276
|
-
lines.push("**输出**:提供 diff 和新模块的依赖关系图。");
|
|
277
|
-
if (!isEmpty(f.testCmd)) {
|
|
278
|
-
lines.push(`**验证**:运行 ${f.testCmd!.trim()} 并确保全部通过。`);
|
|
279
|
-
}
|
|
280
|
-
appendMeta(lines, f);
|
|
281
|
-
return lines.join("\n");
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
interface TestFields {
|
|
285
|
-
filePath: string;
|
|
286
|
-
framework: string;
|
|
287
|
-
coverage: string;
|
|
288
|
-
edgeCases: string;
|
|
289
|
-
testCmd: string;
|
|
290
|
-
acceptance?: string;
|
|
291
|
-
extra?: string;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function assembleTestPrompt(f: TestFields): string {
|
|
295
|
-
const lines: string[] = [];
|
|
296
|
-
lines.push(`[test] 为 ${wrap(f.filePath)} 中的变更生成表驱动测试`);
|
|
297
|
-
lines.push("");
|
|
298
|
-
lines.push(`**角色**:你是一个资深测试工程师。`);
|
|
299
|
-
lines.push(`**背景**:使用 ${wrap(f.framework)} 框架,追求 ≥${wrap(f.coverage, "90")}% 分支覆盖率。`);
|
|
300
|
-
lines.push("**任务**:");
|
|
301
|
-
lines.push(`1. 覆盖维度:${isEmpty(f.edgeCases) ? "null 值、空值、超时、幂等性、重试、成功路径、4xx/5xx 错误、边界条件" : f.edgeCases!.trim()}。`);
|
|
302
|
-
lines.push("2. 优先让测试先失败(红),再提供补丁使其通过(绿)。");
|
|
303
|
-
lines.push("**输出格式**:表格列出场景 → 预期结果 → 权重,末尾附评分模板。");
|
|
304
|
-
lines.push("**约束**:评分准则必须无歧义;不要假设输入总是合法的。");
|
|
305
|
-
if (!isEmpty(f.testCmd)) {
|
|
306
|
-
lines.push(`**验证**:运行 ${f.testCmd!.trim()} 并展示覆盖率报告。`);
|
|
307
|
-
}
|
|
308
|
-
appendMeta(lines, f);
|
|
309
|
-
return lines.join("\n");
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
interface ChoreFields {
|
|
313
|
-
configPath: string;
|
|
314
|
-
task: string;
|
|
315
|
-
envDesc: string;
|
|
316
|
-
targetVersion: string;
|
|
317
|
-
verifyCmd: string;
|
|
318
|
-
acceptance?: string;
|
|
319
|
-
extra?: string;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function assembleChorePrompt(f: ChoreFields): string {
|
|
323
|
-
const lines: string[] = [];
|
|
324
|
-
lines.push(`[chore] 在 ${wrap(f.configPath)} 中 ${wrap(f.task)}`);
|
|
325
|
-
lines.push("");
|
|
326
|
-
lines.push("**角色**:你是一个 DevOps 工程师。");
|
|
327
|
-
lines.push(`**背景**:当前环境 ${wrap(f.envDesc)},目标版本 ${wrap(f.targetVersion)}。`);
|
|
328
|
-
lines.push("**任务**:");
|
|
329
|
-
lines.push(`1. 只做 ${wrap(f.task)},不做任何其他改动。`);
|
|
330
|
-
if (!isEmpty(f.verifyCmd)) {
|
|
331
|
-
lines.push(`2. 改动后运行 ${f.verifyCmd!.trim()} 确认无破坏性变更。`);
|
|
332
|
-
}
|
|
333
|
-
lines.push("**硬性约束**:");
|
|
334
|
-
lines.push("- NEVER 修改生产环境配置文件(如 config/production.yml)。");
|
|
335
|
-
lines.push("- NEVER 运行任何部署命令(除非用户明确要求)。");
|
|
336
|
-
lines.push("- 禁止顺手升级无关依赖、禁止修改代码逻辑。");
|
|
337
|
-
lines.push("**输出**:提供变更前后对比和影响说明。");
|
|
338
|
-
if (!isEmpty(f.verifyCmd)) {
|
|
339
|
-
lines.push(`**验证**:运行 ${f.verifyCmd!.trim()} 并展示结果。`);
|
|
340
|
-
}
|
|
341
|
-
appendMeta(lines, f);
|
|
342
|
-
return lines.join("\n");
|
|
343
|
-
}
|
|
17
|
+
import { confirmIntent } from "./pre-check";
|
|
18
|
+
import { detectProjectDefaults, defaultAcceptanceItems, type ProjectDefaults } from "./session-utils";
|
|
19
|
+
import { uiTaskArg } from "./ui-helpers";
|
|
344
20
|
|
|
345
|
-
interface
|
|
346
|
-
filePath: string;
|
|
347
|
-
bottleneck: string;
|
|
348
|
-
currentCost: string;
|
|
349
|
-
targetLatency: string;
|
|
350
|
-
benchCmd: string;
|
|
351
|
-
acceptance?: string;
|
|
352
|
-
extra?: string;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function assemblePerfPrompt(f: PerfFields): string {
|
|
356
|
-
const lines: string[] = [];
|
|
357
|
-
lines.push(`[perf] 优化 ${wrap(f.filePath)} 中的 ${wrap(f.bottleneck)}`);
|
|
358
|
-
lines.push("");
|
|
359
|
-
lines.push("**角色**:你是一位性能优化专家。");
|
|
360
|
-
lines.push(`**背景**:当前执行耗时约 ${wrap(f.currentCost)},用户可接受的延迟为 ${wrap(f.targetLatency)}。`);
|
|
361
|
-
lines.push("**任务**:");
|
|
362
|
-
lines.push("1. Think deeply about this performance issue.");
|
|
363
|
-
lines.push("2. 先分析当前性能数据,给出基准指标。");
|
|
364
|
-
lines.push("3. 列出 ≥2 种优化方案,分析每个方案的预估提升幅度、实现复杂度、潜在风险。");
|
|
365
|
-
if (!isEmpty(f.benchCmd)) {
|
|
366
|
-
lines.push(`4. 选择推荐方案并实现。优化后运行 ${f.benchCmd!.trim()} 对比前后数据。`);
|
|
367
|
-
} else {
|
|
368
|
-
lines.push("4. 选择推荐方案并实现。优化后运行基准测试对比前后数据。");
|
|
369
|
-
}
|
|
370
|
-
lines.push("**输出**:提供 before/after 性能对比表格。");
|
|
371
|
-
lines.push("**约束**:不要牺牲核心准确性;优先给出低风险改动;不为了微优化牺牲可读性。");
|
|
372
|
-
appendMeta(lines, f);
|
|
373
|
-
return lines.join("\n");
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
interface StyleFields {
|
|
377
|
-
targetStyle: string;
|
|
21
|
+
interface DevType {
|
|
378
22
|
description: string;
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
} else {
|
|
397
|
-
lines.push("2. 输出两种备选风格供我对比。");
|
|
398
|
-
}
|
|
399
|
-
lines.push("**约束**:不要添加原文没有的新事实,不要改变关键数据和逻辑;同时指出原文中可能存在的歧义表达。");
|
|
400
|
-
if (!isEmpty(f.lintCmd)) {
|
|
401
|
-
lines.push(`**验证**:对代码运行 ${f.lintCmd!.trim()} 确保符合规范。`);
|
|
402
|
-
}
|
|
403
|
-
appendMeta(lines, f);
|
|
404
|
-
return lines.join("\n");
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
interface SecurityFields {
|
|
408
|
-
filePath: string;
|
|
409
|
-
focus: string;
|
|
410
|
-
acceptance?: string;
|
|
411
|
-
extra?: string;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function assembleSecurityPrompt(f: SecurityFields): string {
|
|
415
|
-
const lines: string[] = [];
|
|
416
|
-
lines.push(`[security] 对 ${wrap(f.filePath)} 运行安全审查`);
|
|
417
|
-
lines.push("");
|
|
418
|
-
lines.push("**角色**:你是一名安全审计专家(独立于编写代码的 Agent)。");
|
|
419
|
-
lines.push("**任务**:");
|
|
420
|
-
lines.push(`1. 审查清单:${isEmpty(f.focus) ? "认证边界、注入漏洞、敏感数据暴露、CSRF/CORS 配置、权限校验缺失" : f.focus!.trim()}。`);
|
|
421
|
-
lines.push("2. 提供带行号的修复方案及理由。");
|
|
422
|
-
lines.push("3. 只审查不修改,输出审查报告。");
|
|
423
|
-
lines.push("**硬性约束**:在隔离上下文中运行,不继承主 Agent 的记忆。");
|
|
424
|
-
lines.push("**输出**:Markdown 报告,每个问题包含严重级别、行号、风险描述、修复建议。");
|
|
425
|
-
appendMeta(lines, f);
|
|
426
|
-
return lines.join("\n");
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
interface ExplainFields {
|
|
430
|
-
concept: string;
|
|
431
|
-
audience: string;
|
|
432
|
-
depth: string;
|
|
433
|
-
acceptance?: string;
|
|
434
|
-
extra?: string;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function assembleExplainPrompt(f: ExplainFields): string {
|
|
438
|
-
const lines: string[] = [];
|
|
439
|
-
lines.push(`[explain] 解释 ${wrap(f.concept)}`);
|
|
440
|
-
lines.push("");
|
|
441
|
-
lines.push("**角色**:你是一位资深技术导师,擅长用类比引导初学者理解复杂概念。");
|
|
442
|
-
lines.push(`**背景**:目标受众是 ${wrap(f.audience)},需要理解 ${wrap(f.concept)} 的 ${wrap(f.depth, "基础")} 层面。`);
|
|
443
|
-
lines.push("**任务**:");
|
|
444
|
-
lines.push("1. 用生活化的类比引入概念,建立直觉。");
|
|
445
|
-
lines.push("2. 由浅入深逐步展开,先给大局观再进入细节。");
|
|
446
|
-
lines.push("3. 提供至少一个真实世界的应用场景。");
|
|
447
|
-
lines.push("4. 如有常见误区,明确指出。");
|
|
448
|
-
lines.push("**输出格式**:Markdown,必要时插入图示描述。");
|
|
449
|
-
lines.push("**验证**:请先给出一句话总结,经我确认后再展开。");
|
|
450
|
-
appendMeta(lines, f);
|
|
451
|
-
return lines.join("\n");
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
interface CompareFields {
|
|
455
|
-
itemA: string;
|
|
456
|
-
itemB: string;
|
|
457
|
-
dimensions: string;
|
|
458
|
-
acceptance?: string;
|
|
459
|
-
extra?: string;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function assembleComparePrompt(f: CompareFields): string {
|
|
463
|
-
const lines: string[] = [];
|
|
464
|
-
lines.push(`[compare] 对比 ${wrap(f.itemA)} 与 ${wrap(f.itemB)}`);
|
|
465
|
-
lines.push("");
|
|
466
|
-
lines.push("**角色**:你是一位客观中立的评测专家。");
|
|
467
|
-
lines.push(`**背景**:需要从 ${wrap(f.dimensions, "多个方面")} 对 ${wrap(f.itemA)} 和 ${wrap(f.itemB)} 进行全面对比。`);
|
|
468
|
-
lines.push("**任务**:");
|
|
469
|
-
lines.push("1. 构建多维度评估矩阵,量化或半量化评分。");
|
|
470
|
-
lines.push("2. 分析各维度的权衡(Trade-offs),说明在什么场景下哪个更优。");
|
|
471
|
-
lines.push("3. 给出综合结论和建议。");
|
|
472
|
-
lines.push("**输出格式**:Markdown 表格 + 简短分析。");
|
|
473
|
-
lines.push("**约束**:客观中立,不偏袒任何一方,明确标注不确定的结论。");
|
|
474
|
-
appendMeta(lines, f);
|
|
475
|
-
return lines.join("\n");
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// ── 四段式收尾:验收标准 + 额外补充 ───────────────────────────
|
|
479
|
-
|
|
480
|
-
/** 追加“验收标准 / 额外补充”段落(用户填写或默认填充,保证提示词不空白)。 */
|
|
481
|
-
function appendMeta(lines: string[], f: { acceptance?: string; extra?: string }): void {
|
|
482
|
-
if (f.acceptance?.trim()) {
|
|
483
|
-
lines.push("");
|
|
484
|
-
lines.push(`**验收标准**:${f.acceptance.trim()}`);
|
|
485
|
-
}
|
|
486
|
-
if (f.extra?.trim()) {
|
|
487
|
-
lines.push("");
|
|
488
|
-
lines.push(`**额外补充**:${f.extra.trim()}`);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
23
|
+
/** 身份描述,拼在「你是…」之后。 */
|
|
24
|
+
role: (d: ProjectDefaults) => string;
|
|
25
|
+
/** 职责清单。 */
|
|
26
|
+
duties: string[];
|
|
27
|
+
/** 在项目默认验收标准之上追加的条目。 */
|
|
28
|
+
extraAcceptance?: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const FEAT: DevType = {
|
|
32
|
+
description: "(dev) 新功能实现 — 先复述确认意图,再按 身份职责 + 默认验收标准 组装提示词执行",
|
|
33
|
+
role: (d) => `资深 ${d.language || "项目"} 工程师`,
|
|
34
|
+
duties: [
|
|
35
|
+
"先读代码库再动手:给出逐步实施计划(要修改/新建的文件、迁移、对现有代码的假设),经我确认后再写代码",
|
|
36
|
+
"只实现任务要求的功能,不顺手重构无关代码",
|
|
37
|
+
"保持现有公共 API 兼容,不为假设性需求添加抽象层",
|
|
38
|
+
],
|
|
39
|
+
};
|
|
491
40
|
|
|
492
|
-
|
|
41
|
+
const FIX: DevType = {
|
|
42
|
+
description: "(dev) 问题修复 — 先复述确认意图,再按 根因定位职责 + 默认验收标准 组装提示词执行",
|
|
43
|
+
role: (d) => `资深 ${d.language || "项目"} 调试工程师`,
|
|
44
|
+
duties: [
|
|
45
|
+
"先读相关代码与日志定位根因,不用消除报错的方式掩盖症状",
|
|
46
|
+
"修复前说明根因与方案取舍,只改与该问题相关的代码",
|
|
47
|
+
"补一个能复现该问题的测试",
|
|
48
|
+
],
|
|
49
|
+
extraAcceptance: ["新增的复现测试在修复前失败、修复后通过"],
|
|
50
|
+
};
|
|
493
51
|
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
actualError: "当前实际的报错信息",
|
|
504
|
-
testCmd: d.testCmd,
|
|
505
|
-
}),
|
|
506
|
-
doc: (d) => ({
|
|
507
|
-
audience: "目标读者",
|
|
508
|
-
keyInfo: "该模块的核心用法与关键概念",
|
|
509
|
-
language: d.language || "项目语言",
|
|
510
|
-
}),
|
|
511
|
-
refactor: (d) => ({
|
|
512
|
-
problems: "可读性/可维护性等结构性问题",
|
|
513
|
-
goal: "可读性与可维护性",
|
|
514
|
-
testCmd: d.testCmd,
|
|
515
|
-
}),
|
|
516
|
-
test: (d) => ({
|
|
517
|
-
framework: "项目采用的测试框架",
|
|
518
|
-
coverage: "90",
|
|
519
|
-
edgeCases: "null 值、空值、超时、幂等性、重试、成功路径、4xx/5xx 错误、边界条件",
|
|
520
|
-
testCmd: d.testCmd,
|
|
521
|
-
}),
|
|
522
|
-
chore: (d) => ({
|
|
523
|
-
envDesc: "项目运行环境",
|
|
524
|
-
targetVersion: "按任务指定的版本",
|
|
525
|
-
verifyCmd: d.testCmd || d.lintCmd,
|
|
526
|
-
}),
|
|
527
|
-
perf: () => ({
|
|
528
|
-
bottleneck: "性能瓶颈",
|
|
529
|
-
currentCost: "需先测量基准",
|
|
530
|
-
targetLatency: "按需求设定的延迟",
|
|
531
|
-
benchCmd: "",
|
|
532
|
-
}),
|
|
533
|
-
style: (d) => ({
|
|
534
|
-
targetStyle: "简洁清晰的风格",
|
|
535
|
-
lintCmd: d.lintCmd,
|
|
536
|
-
}),
|
|
537
|
-
security: () => ({
|
|
538
|
-
focus: "认证边界、注入漏洞、敏感数据暴露、CSRF/CORS 配置、权限校验缺失",
|
|
539
|
-
}),
|
|
540
|
-
explain: () => ({
|
|
541
|
-
audience: "对概念感兴趣的技术读者",
|
|
542
|
-
depth: "基础",
|
|
543
|
-
}),
|
|
544
|
-
compare: () => ({
|
|
545
|
-
dimensions: "性能、生态、学习曲线、社区支持",
|
|
546
|
-
}),
|
|
52
|
+
const REFACTOR: DevType = {
|
|
53
|
+
description: "(dev) 重构 — 先复述确认意图,再按 行为不变职责 + 默认验收标准 组装提示词执行",
|
|
54
|
+
role: (d) => `资深 ${d.language || "项目"} 工程师`,
|
|
55
|
+
duties: [
|
|
56
|
+
"重构前后对外行为与公共 API 签名完全一致",
|
|
57
|
+
"先说明要消除的具体结构性问题再动手;不做与目标无关的调整",
|
|
58
|
+
"不新增功能、不修改业务逻辑",
|
|
59
|
+
],
|
|
60
|
+
extraAcceptance: ["对外行为与公共 API 不变,现有测试全部通过"],
|
|
547
61
|
};
|
|
548
62
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
63
|
+
const TEST: DevType = {
|
|
64
|
+
description: "(dev) 测试补充 — 先复述确认意图,再按 测试职责 + 默认验收标准 组装提示词执行",
|
|
65
|
+
role: () => "资深测试工程师",
|
|
66
|
+
duties: [
|
|
67
|
+
"覆盖成功路径、边界条件与错误分支,测试必须真实执行",
|
|
68
|
+
"断言明确无歧义,不写只验证 mock 的测试",
|
|
69
|
+
"保持与项目既有测试框架和风格一致",
|
|
70
|
+
],
|
|
71
|
+
extraAcceptance: ["新增测试覆盖成功路径、边界条件与错误分支,且全部通过"],
|
|
72
|
+
};
|
|
557
73
|
|
|
558
|
-
/**
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
74
|
+
/** 取任务首行作为提示词标题,避免把整段需求塞进标题。 */
|
|
75
|
+
function taskSummary(task: string): string {
|
|
76
|
+
return task.split("\n")[0]!.trim().slice(0, 100);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildDevPrompt(
|
|
80
|
+
command: string,
|
|
81
|
+
type: DevType,
|
|
82
|
+
task: string,
|
|
83
|
+
intent: string,
|
|
84
|
+
d: ProjectDefaults,
|
|
85
|
+
): string {
|
|
86
|
+
const acceptance = [...defaultAcceptanceItems(d), ...(type.extraAcceptance ?? [])];
|
|
87
|
+
return [
|
|
88
|
+
`[${command}] ${taskSummary(task)}`,
|
|
89
|
+
"",
|
|
90
|
+
"## 任务(原始描述)",
|
|
91
|
+
task,
|
|
92
|
+
"",
|
|
93
|
+
"## 已确认的任务意图",
|
|
94
|
+
intent,
|
|
95
|
+
"",
|
|
96
|
+
"## 身份与职责",
|
|
97
|
+
`你是${type.role(d)}。`,
|
|
98
|
+
...type.duties.map((duty) => `- ${duty}`),
|
|
99
|
+
"",
|
|
100
|
+
"## 验收标准",
|
|
101
|
+
"以下为默认收尾验收基线;任务描述中另有明确验收标准时,以任务描述为准。",
|
|
102
|
+
...acceptance.map((item) => `- ${item}`),
|
|
103
|
+
].join("\n");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function runDevCommand(
|
|
563
107
|
pi: ExtensionAPI,
|
|
564
|
-
type: string,
|
|
565
|
-
label: string,
|
|
566
|
-
questions: WizardQuestion[],
|
|
567
|
-
assembler: (answers: Record<string, string>) => string,
|
|
568
|
-
): Promise<void> {
|
|
569
|
-
const answers: Record<string, string> = {};
|
|
570
|
-
let idx = 0;
|
|
571
|
-
|
|
572
|
-
while (idx >= 0 && idx < questions.length) {
|
|
573
|
-
const q = questions[idx]!;
|
|
574
|
-
const existingVal = answers[q.keys[0]!];
|
|
575
|
-
const placeholder = existingVal
|
|
576
|
-
? `(之前: ${existingVal.slice(0, 60)}) ${q.placeholder}`
|
|
577
|
-
: q.placeholder;
|
|
578
|
-
const val = await ask(ctx, q.label, placeholder, true, existingVal || "");
|
|
579
|
-
if (val === undefined) {
|
|
580
|
-
// Esc → cancel whole wizard
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
if (val === BACK_MARKER) {
|
|
584
|
-
// Go back to previous question
|
|
585
|
-
if (idx > 0) {
|
|
586
|
-
idx--;
|
|
587
|
-
continue;
|
|
588
|
-
}
|
|
589
|
-
// Already at first question → cancel
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
assignAnswers(answers, q, val);
|
|
593
|
-
idx++;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
applyDefaults(answers, type, detectProjectDefaults(ctx.cwd));
|
|
597
|
-
const prompt = assembler(answers);
|
|
598
|
-
|
|
599
|
-
// ── Guard & persist before sending ──────────────────────
|
|
600
|
-
const finalPrompt = prompt || recoverFromBackup(ctx.cwd) || "";
|
|
601
|
-
if (!finalPrompt) return;
|
|
602
|
-
|
|
603
|
-
saveAnswerFile(ctx.cwd, finalPrompt);
|
|
604
|
-
pi.sendUserMessage(finalPrompt, { deliverAs: "followUp" });
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Run a wizard with an optional Grill phase, then send to the current agent.
|
|
609
|
-
*/
|
|
610
|
-
async function runWizardWithGrill(
|
|
611
108
|
ctx: ExtensionCommandContext,
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
questions: WizardQuestion[],
|
|
616
|
-
assembler: (answers: Record<string, string>) => string,
|
|
617
|
-
grillOptions?: GrillOptions,
|
|
109
|
+
args: string,
|
|
110
|
+
command: string,
|
|
111
|
+
type: DevType,
|
|
618
112
|
): Promise<void> {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
while (idx >= 0 && idx < questions.length) {
|
|
623
|
-
const q = questions[idx]!;
|
|
624
|
-
const existingVal = answers[q.keys[0]!];
|
|
625
|
-
const placeholder = existingVal
|
|
626
|
-
? `(之前: ${existingVal.slice(0, 60)}) ${q.placeholder}`
|
|
627
|
-
: q.placeholder;
|
|
628
|
-
const val = await ask(ctx, q.label, placeholder, true, existingVal || "");
|
|
629
|
-
if (val === undefined) {
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
if (val === BACK_MARKER) {
|
|
633
|
-
if (idx > 0) {
|
|
634
|
-
idx--;
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
assignAnswers(answers, q, val);
|
|
640
|
-
idx++;
|
|
113
|
+
if (!ctx.hasUI) {
|
|
114
|
+
ctx.ui.notify(`/${command} 需要交互式界面才能确认意图`, "error");
|
|
115
|
+
return;
|
|
641
116
|
}
|
|
642
117
|
|
|
643
|
-
|
|
644
|
-
|
|
118
|
+
const task = await uiTaskArg(ctx, args, "任务描述(必填)", "如 实现邮箱密码登录接口");
|
|
119
|
+
if (!task) return;
|
|
645
120
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
title: grillOptions.title,
|
|
651
|
-
description: grillOptions.description,
|
|
652
|
-
questionTitle: grillOptions.questionTitle,
|
|
653
|
-
});
|
|
654
|
-
if (grillResult.cancelled) {
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
finalPrompt = grillResult.enhancedPrompt;
|
|
121
|
+
const intent = await confirmIntent(task, ctx, pi);
|
|
122
|
+
if (!intent) {
|
|
123
|
+
ctx.ui.notify("已取消,未执行任何改动", "info");
|
|
124
|
+
return;
|
|
658
125
|
}
|
|
659
126
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if (recovered) {
|
|
664
|
-
finalPrompt = recovered;
|
|
665
|
-
} else {
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
saveAnswerFile(ctx.cwd, finalPrompt);
|
|
670
|
-
pi.sendUserMessage(finalPrompt, { deliverAs: "followUp" });
|
|
127
|
+
const prompt = buildDevPrompt(command, type, task, intent, detectProjectDefaults(ctx.cwd));
|
|
128
|
+
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
129
|
+
ctx.ui.notify(`意图已确认,/${command} 提示词已发送给当前代理`, "info");
|
|
671
130
|
}
|
|
672
131
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认:项目测试 + lint + pre-commit + CI 常规验收", keys: ["acceptance"] },
|
|
679
|
-
{ label: "额外补充(可跳过)", placeholder: "如 目标模块 src/auth/login.ts、技术栈 NestJS、当前痛点、测试命令等", keys: ["extra"] },
|
|
680
|
-
];
|
|
681
|
-
|
|
682
|
-
const FIX_QUESTIONS: WizardQuestion[] = [
|
|
683
|
-
{ label: "问题文件路径", placeholder: "如 src/auth/login.ts(可带 #L42)", keys: ["filePath"] },
|
|
684
|
-
{ label: "Bug 现象描述", placeholder: "如 登录接口在密码正确时返回 401", keys: ["bugDesc"] },
|
|
685
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准(测试/lint/pre-commit/CI)", keys: ["acceptance"] },
|
|
686
|
-
{ label: "额外补充(可跳过)", placeholder: "如 输入现象、预期行为、当前报错、测试命令等", keys: ["extra"] },
|
|
687
|
-
];
|
|
688
|
-
|
|
689
|
-
const DOC_QUESTIONS: WizardQuestion[] = [
|
|
690
|
-
{ label: "文档对象(模块/API/doc 名称)", placeholder: "如 REST API v2、AuthService", keys: ["moduleName"] },
|
|
691
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准", keys: ["acceptance"] },
|
|
692
|
-
{ label: "额外补充(可跳过)", placeholder: "如 目标受众、关键信息点、示例语言、已有材料", keys: ["extra"] },
|
|
693
|
-
];
|
|
694
|
-
|
|
695
|
-
const REFACTOR_QUESTIONS: WizardQuestion[] = [
|
|
696
|
-
{ label: "文件路径", placeholder: "如 src/auth/login.ts(可标约行数)", keys: ["filePath"] },
|
|
697
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准", keys: ["acceptance"] },
|
|
698
|
-
{ label: "额外补充(可跳过)", placeholder: "如 具体问题、重构目标、测试命令", keys: ["extra"] },
|
|
699
|
-
];
|
|
700
|
-
|
|
701
|
-
const TEST_QUESTIONS: WizardQuestion[] = [
|
|
702
|
-
{ label: "文件路径", placeholder: "如 src/auth/login.ts", keys: ["filePath"] },
|
|
703
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准(含覆盖率与边界条件)", keys: ["acceptance"] },
|
|
704
|
-
{ label: "额外补充(可跳过)", placeholder: "如 测试框架、目标覆盖率、重点边界条件、测试命令", keys: ["extra"] },
|
|
705
|
-
];
|
|
706
|
-
|
|
707
|
-
const CHORE_QUESTIONS: WizardQuestion[] = [
|
|
708
|
-
{ label: "配置文件/目标路径", placeholder: "如 package.json、.github/workflows/ci.yml", keys: ["configPath"] },
|
|
709
|
-
{ label: "具体任务", placeholder: "如 升级 eslint 到 v9、调整构建脚本", keys: ["task"] },
|
|
710
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准", keys: ["acceptance"] },
|
|
711
|
-
{ label: "额外补充(可跳过)", placeholder: "如 当前环境、目标版本、验证命令", keys: ["extra"] },
|
|
712
|
-
];
|
|
713
|
-
|
|
714
|
-
const PERF_QUESTIONS: WizardQuestion[] = [
|
|
715
|
-
{ label: "文件路径", placeholder: "如 src/services/query.ts", keys: ["filePath"] },
|
|
716
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准(含基准对比)", keys: ["acceptance"] },
|
|
717
|
-
{ label: "额外补充(可跳过)", placeholder: "如 瓶颈、当前耗时/成本、目标延迟、基准命令", keys: ["extra"] },
|
|
718
|
-
];
|
|
719
|
-
|
|
720
|
-
const STYLE_QUESTIONS: WizardQuestion[] = [
|
|
721
|
-
{ label: "待调整内容", placeholder: "如 调整以下函数的命名风格,或粘贴文本", keys: ["description"] },
|
|
722
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准(含 lint)", keys: ["acceptance"] },
|
|
723
|
-
{ label: "额外补充(可跳过)", placeholder: "如 目标风格、术语统一、校验命令", keys: ["extra"] },
|
|
724
|
-
];
|
|
725
|
-
|
|
726
|
-
const SECURITY_QUESTIONS: WizardQuestion[] = [
|
|
727
|
-
{ label: "文件路径", placeholder: "如 src/api/auth.ts", keys: ["filePath"] },
|
|
728
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认安全审查清单", keys: ["acceptance"] },
|
|
729
|
-
];
|
|
730
|
-
|
|
731
|
-
const EXPLAIN_QUESTIONS: WizardQuestion[] = [
|
|
732
|
-
{ label: "概念名称", placeholder: "如 React Server Component、HTTP/3", keys: ["concept"] },
|
|
733
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准", keys: ["acceptance"] },
|
|
734
|
-
{ label: "额外补充(可跳过)", placeholder: "如 目标受众、理解深度", keys: ["extra"] },
|
|
735
|
-
];
|
|
736
|
-
|
|
737
|
-
const COMPARE_QUESTIONS: WizardQuestion[] = [
|
|
738
|
-
{ label: "对比对象 A", placeholder: "如 Vue 3", keys: ["itemA"] },
|
|
739
|
-
{ label: "对比对象 B", placeholder: "如 React 18", keys: ["itemB"] },
|
|
740
|
-
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准", keys: ["acceptance"] },
|
|
741
|
-
{ label: "额外补充(可跳过)", placeholder: "如 评估维度(性能/生态/学习曲线等)", keys: ["extra"] },
|
|
742
|
-
];
|
|
743
|
-
|
|
744
|
-
// ── Extension ────────────────────────────────────────────────
|
|
745
|
-
|
|
746
|
-
export default function (pi: ExtensionAPI) {
|
|
747
|
-
// ── Auto review detection (runs in the current agent) ─────────
|
|
748
|
-
pi.on("input", async (event, ctx) => {
|
|
749
|
-
if (!ctx.hasUI) return { action: "continue" };
|
|
750
|
-
// 扩展注入的消息(sendUserMessage,source 为 "extension")不重入本处理器,避免无限递归
|
|
751
|
-
if (event.source === "extension") return { action: "continue" };
|
|
752
|
-
|
|
753
|
-
const text = event.text.trim().toLowerCase();
|
|
754
|
-
|
|
755
|
-
// Detect review-html skill invocation or explicit review request.
|
|
756
|
-
const isReviewSkill = text.startsWith("/skill:review-html");
|
|
757
|
-
const hasReviewIntent = text.includes("review") ||
|
|
758
|
-
text.includes("审查") || text.includes("审阅") || text.includes("review-html");
|
|
759
|
-
const hasCodeTarget = text.includes("code") || text.includes("代码") ||
|
|
760
|
-
text.includes("diff") || text.includes("commit") ||
|
|
761
|
-
text.includes("html") || text.includes("report") || text.includes("报告") ||
|
|
762
|
-
text.includes("本次改动") || text.includes("这次改动");
|
|
763
|
-
const isReviewRequest = !isReviewSkill && hasReviewIntent && hasCodeTarget;
|
|
764
|
-
|
|
765
|
-
if (!isReviewSkill && !isReviewRequest) return { action: "continue" };
|
|
766
|
-
|
|
767
|
-
// ── Skill invocation: run review directly ─────────────────
|
|
768
|
-
if (isReviewSkill) {
|
|
769
|
-
await runReview(event.text, ctx, pi);
|
|
770
|
-
return { action: "handled" };
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// ── Review intent: let the user choose ────────────────────
|
|
774
|
-
const mode = await uiSelect(
|
|
775
|
-
ctx,
|
|
776
|
-
"🔍 检测到审查意图",
|
|
777
|
-
[
|
|
778
|
-
"1. 开始审查(阻塞,等待结果)",
|
|
779
|
-
"2. 不是审查(放行给主代理)",
|
|
780
|
-
],
|
|
781
|
-
);
|
|
782
|
-
|
|
783
|
-
if (!mode || mode.startsWith("2")) {
|
|
784
|
-
return { action: "continue" };
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
await runReview(event.text, ctx, pi);
|
|
788
|
-
|
|
789
|
-
return { action: "handled" };
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
// ── /dev-feat ──────────────────────────────────────────────
|
|
793
|
-
pi.registerCommand("dev-feat", {
|
|
794
|
-
description: "(prompt wizard) 新功能/创意生成 — 支持设计方案追问完善 (Grill)",
|
|
795
|
-
handler: async (_args, ctx) => {
|
|
796
|
-
const answers: Record<string, string> = {};
|
|
797
|
-
let featIdx = 0;
|
|
798
|
-
while (featIdx >= 0 && featIdx < FEAT_QUESTIONS.length) {
|
|
799
|
-
const q = FEAT_QUESTIONS[featIdx]!;
|
|
800
|
-
const existingVal = answers[q.keys[0]!];
|
|
801
|
-
const placeholder = existingVal
|
|
802
|
-
? `(之前: ${existingVal.slice(0, 60)}) ${q.placeholder}`
|
|
803
|
-
: q.placeholder;
|
|
804
|
-
const val = await ask(ctx, q.label, placeholder, true, existingVal || "");
|
|
805
|
-
if (val === undefined) {
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
if (val === BACK_MARKER) {
|
|
809
|
-
if (featIdx > 0) {
|
|
810
|
-
featIdx--;
|
|
811
|
-
continue;
|
|
812
|
-
}
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
assignAnswers(answers, q, val);
|
|
816
|
-
featIdx++;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
applyDefaults(answers, "feat", detectProjectDefaults(ctx.cwd));
|
|
820
|
-
const basePrompt = assembleFeatPrompt(answers as FeatFields);
|
|
821
|
-
|
|
822
|
-
const grillResult = await runGrillPhase(basePrompt, ctx, pi, {
|
|
823
|
-
title: "🔍 设计方案追问完善",
|
|
824
|
-
description: "AI 会通过系统性追问帮你打磨方案:从术语精确化到边界条件验证,确保架构决策的每个分支都经过推敲。",
|
|
825
|
-
questionTitle: "设计方案追问完善",
|
|
826
|
-
});
|
|
827
|
-
if (grillResult.cancelled) {
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
const finalPrompt = grillResult.enhancedPrompt || basePrompt;
|
|
831
|
-
|
|
832
|
-
// ── PRD phase (current agent) ──────────────────────────
|
|
833
|
-
await runPRDPhase(finalPrompt, (answers as FeatFields).module || "feature", pi, ctx);
|
|
834
|
-
|
|
835
|
-
saveAnswerFile(ctx.cwd, finalPrompt);
|
|
836
|
-
pi.sendUserMessage(finalPrompt, { deliverAs: "followUp" });
|
|
837
|
-
},
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
// ── /dev-fix ───────────────────────────────────────────────
|
|
841
|
-
pi.registerCommand("dev-fix", {
|
|
842
|
-
description: "(prompt wizard) 问题排查/错误修正 — 支持 Bug 根因追问 (Grill)",
|
|
843
|
-
handler: async (_args, ctx) => {
|
|
844
|
-
await runWizardWithGrill(
|
|
845
|
-
ctx, pi, "fix", "问题排查/错误修正",
|
|
846
|
-
FIX_QUESTIONS, assembleFixPrompt,
|
|
847
|
-
{
|
|
848
|
-
title: "🐛 Bug 根因追问",
|
|
849
|
-
description: "AI 会通过系统性追问帮你精准定位根因:从复现条件到根本原因推理,再到修复方案验证和回归风险评估。",
|
|
850
|
-
questionTitle: "Bug 根因分析",
|
|
851
|
-
},
|
|
852
|
-
);
|
|
853
|
-
},
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
// ── /dev-doc ───────────────────────────────────────────────
|
|
857
|
-
pi.registerCommand("dev-doc", {
|
|
858
|
-
description: "(prompt wizard) 文档生成/总结 — 支持文档大纲追问完善 (Grill)",
|
|
859
|
-
handler: async (_args, ctx) => {
|
|
860
|
-
await runWizardWithGrill(
|
|
861
|
-
ctx, pi, "doc", "文档生成/总结",
|
|
862
|
-
DOC_QUESTIONS, assembleDocPrompt,
|
|
863
|
-
{
|
|
864
|
-
title: "📄 文档大纲追问完善",
|
|
865
|
-
description: "AI 会通过追问帮你完善文档大纲:从受众定位到结构安排,确认术语一致性和示例覆盖范围。",
|
|
866
|
-
questionTitle: "文档大纲追问完善",
|
|
867
|
-
},
|
|
868
|
-
);
|
|
869
|
-
},
|
|
870
|
-
});
|
|
871
|
-
|
|
872
|
-
// ── /dev-refactor ──────────────────────────────────────────
|
|
873
|
-
pi.registerCommand("dev-refactor", {
|
|
874
|
-
description: "(prompt wizard) 重构/优化现有结构 — 支持重构方案追问 (Grill)",
|
|
875
|
-
handler: async (_args, ctx) => {
|
|
876
|
-
await runWizardWithGrill(
|
|
877
|
-
ctx, pi, "refactor", "重构/优化",
|
|
878
|
-
REFACTOR_QUESTIONS, assembleRefactorPrompt,
|
|
879
|
-
{
|
|
880
|
-
title: "🔧 重构方案追问",
|
|
881
|
-
description: "AI 会通过追问帮你识别隐藏耦合风险:从模块边界到 API 兼容性,验证行为保持和迁移路径安全性。",
|
|
882
|
-
questionTitle: "重构方案追问",
|
|
883
|
-
},
|
|
884
|
-
);
|
|
885
|
-
},
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
// ── /dev-test ──────────────────────────────────────────────
|
|
889
|
-
pi.registerCommand("dev-test", {
|
|
890
|
-
description: "(prompt wizard) 测试用例生成 — 支持测试策略追问 (Grill)",
|
|
891
|
-
handler: async (_args, ctx) => {
|
|
892
|
-
await runWizardWithGrill(
|
|
893
|
-
ctx, pi, "test", "测试用例/评估",
|
|
894
|
-
TEST_QUESTIONS, assembleTestPrompt,
|
|
895
|
-
{
|
|
896
|
-
title: "🧪 测试策略追问",
|
|
897
|
-
description: "AI 会通过追问帮你发现测试缺口:从覆盖维度到边界条件,验证模拟策略和测试隔离是否到位。",
|
|
898
|
-
questionTitle: "测试策略追问",
|
|
899
|
-
},
|
|
900
|
-
);
|
|
901
|
-
},
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
// ── /dev-chore ─────────────────────────────────────────────
|
|
905
|
-
pi.registerCommand("dev-chore", {
|
|
906
|
-
description: "(prompt wizard) 日常维护/杂项自动化 — 交互填写后发送优化提示词给主代理",
|
|
907
|
-
handler: async (_args, ctx) => {
|
|
908
|
-
await runWizard(ctx, pi, "chore", "日常维护/自动化", CHORE_QUESTIONS, assembleChorePrompt);
|
|
909
|
-
},
|
|
910
|
-
});
|
|
911
|
-
|
|
912
|
-
// ── /dev-perf ──────────────────────────────────────────────
|
|
913
|
-
pi.registerCommand("dev-perf", {
|
|
914
|
-
description: "(prompt wizard) 性能优化 — 支持性能优化方案追问 (Grill)",
|
|
915
|
-
handler: async (_args, ctx) => {
|
|
916
|
-
await runWizardWithGrill(
|
|
917
|
-
ctx, pi, "perf", "性能优化",
|
|
918
|
-
PERF_QUESTIONS, assemblePerfPrompt,
|
|
919
|
-
{
|
|
920
|
-
title: "⚡ 性能优化方案追问",
|
|
921
|
-
description: "AI 会通过追问帮你验证瓶颈判断和优化方向:从基准测试方法到潜在回归风险,确保方案合理性。",
|
|
922
|
-
questionTitle: "性能优化方案追问",
|
|
923
|
-
},
|
|
924
|
-
);
|
|
925
|
-
},
|
|
926
|
-
});
|
|
927
|
-
|
|
928
|
-
// ── /dev-style ─────────────────────────────────────────────
|
|
929
|
-
pi.registerCommand("dev-style", {
|
|
930
|
-
description: "(prompt wizard) 风格/格式调整 — 交互填写后发送优化提示词给主代理",
|
|
931
|
-
handler: async (_args, ctx) => {
|
|
932
|
-
await runWizard(ctx, pi, "style", "风格/格式调整", STYLE_QUESTIONS, assembleStylePrompt);
|
|
933
|
-
},
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
// ── /dev-security ──────────────────────────────────────────
|
|
937
|
-
pi.registerCommand("dev-security", {
|
|
938
|
-
description: "(prompt wizard) 安全审查 — 交互填写后发送优化提示词给主代理",
|
|
939
|
-
handler: async (_args, ctx) => {
|
|
940
|
-
await runWizard(ctx, pi, "security", "安全审查", SECURITY_QUESTIONS, assembleSecurityPrompt);
|
|
941
|
-
},
|
|
942
|
-
});
|
|
943
|
-
|
|
944
|
-
// ── /dev-explain ───────────────────────────────────────────
|
|
945
|
-
pi.registerCommand("dev-explain", {
|
|
946
|
-
description: "(prompt wizard) 概念解释 — 交互填写后发送优化提示词给主代理",
|
|
947
|
-
handler: async (_args, ctx) => {
|
|
948
|
-
await runWizard(ctx, pi, "explain", "概念解释", EXPLAIN_QUESTIONS, assembleExplainPrompt);
|
|
132
|
+
function registerDev(pi: ExtensionAPI, command: string, type: DevType): void {
|
|
133
|
+
pi.registerCommand(command, {
|
|
134
|
+
description: type.description,
|
|
135
|
+
handler: async (args, ctx) => {
|
|
136
|
+
await runDevCommand(pi, ctx, args, command, type);
|
|
949
137
|
},
|
|
950
138
|
});
|
|
139
|
+
}
|
|
951
140
|
|
|
952
|
-
|
|
953
|
-
pi
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
},
|
|
958
|
-
});
|
|
141
|
+
export default function (pi: ExtensionAPI) {
|
|
142
|
+
registerDev(pi, "dev-feat", FEAT);
|
|
143
|
+
registerDev(pi, "dev-fix", FIX);
|
|
144
|
+
registerDev(pi, "dev-refactor", REFACTOR);
|
|
145
|
+
registerDev(pi, "dev-test", TEST);
|
|
959
146
|
}
|