@ghyper9023/pi-dev-workflow 0.6.2 → 0.7.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/.github/workflows/release.yml +56 -0
- package/.pre-commit-config.yaml +75 -0
- package/.version/RELEASE-v0.7.0.md +85 -0
- package/README.md +80 -228
- package/extensions/dev-prompts.ts +351 -450
- package/extensions/git-commands.ts +62 -171
- package/extensions/grill-me-agent.ts +98 -137
- package/extensions/session-utils.ts +169 -0
- package/extensions/ui-helpers.ts +2 -780
- package/package.json +2 -2
- package/prompts/APPEND_SYSTEM.md +63 -59
- package/skills/review-html/SKILL.md +1 -1
- package/tests/test-no-subagents.mjs +146 -0
- package/.doc/AGENT-FRONTMATTER-REFERENCE.md +0 -198
- package/agents/git-agent.md +0 -44
- package/agents/grill/dev-doc-grill-agent.md +0 -42
- package/agents/grill/dev-fix-grill-agent.md +0 -44
- package/agents/grill/dev-grill-agent.md +0 -40
- package/agents/grill/dev-perf-grill-agent.md +0 -45
- package/agents/grill/dev-prd-agent.md +0 -60
- package/agents/grill/dev-refactor-grill-agent.md +0 -46
- package/agents/grill/dev-test-grill-agent.md +0 -45
- package/agents/review-agent.md +0 -53
- package/agents/workflow/docWriter-agent.md +0 -53
- package/agents/workflow/planner-agent.md +0 -131
- package/agents/workflow/reviewer-agent.md +0 -128
- package/agents/workflow/trimmer-agent.md +0 -78
- package/agents/workflow/worker-agent.md +0 -70
- package/extensions/sub-agents.ts +0 -954
- package/extensions/workflow-engine.ts +0 -2005
- package/tests/test-grill-json-fix.mjs +0 -243
- package/tests/test-loopcount-timeout-fix.mjs +0 -336
- package/tests/test-output-directory-structure.mjs +0 -177
- package/tests/test-save-answer-file-workflow.mjs +0 -187
- package/tests/test-workflow-config.mjs +0 -244
- package/tests/test-workflow-engine-bugs.mjs +0 -908
- package/tests/test-workflow-engine.mjs +0 -518
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registers /dev-* commands that interactively collect missing context for
|
|
5
5
|
* high-quality prompt templates (from ai提示词优化.md) and send the assembled
|
|
6
|
-
* prompt directly to the
|
|
6
|
+
* prompt directly to the current agent.
|
|
7
7
|
*
|
|
8
8
|
* Commands:
|
|
9
9
|
* /dev-feat - New feature / creative generation
|
|
@@ -21,12 +21,16 @@
|
|
|
21
21
|
* Usage: type /dev-<type> and follow the wizard.
|
|
22
22
|
* Leave a field empty (Enter) to skip its section.
|
|
23
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.
|
|
24
27
|
*/
|
|
25
28
|
|
|
29
|
+
import * as fs from "node:fs";
|
|
30
|
+
import * as path from "node:path";
|
|
26
31
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
27
32
|
import { runGrillPhase, runPRDPhase, saveAnswerFile, recoverFromBackup, type GrillOptions } from "./grill-me-agent";
|
|
28
|
-
import {
|
|
29
|
-
import { runWorkflow, loadCheckpointFromFile, type WorkflowStepDef } from "./workflow-engine";
|
|
33
|
+
import { detectProjectDefaults, defaultAcceptance, type ProjectDefaults } from "./session-utils";
|
|
30
34
|
import { uiSelect, uiConfirm, uiInput, BACK_MARKER } from "./ui-helpers";
|
|
31
35
|
|
|
32
36
|
// ── Helpers ──────────────────────────────────────────────────
|
|
@@ -42,6 +46,25 @@ async function ask(
|
|
|
42
46
|
return uiInput(ctx, label, placeholder, false, backable, initialValue);
|
|
43
47
|
}
|
|
44
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
|
+
|
|
45
68
|
/** Check if a field value is empty or explicitly "无". */
|
|
46
69
|
function isEmpty(val: string | undefined): boolean {
|
|
47
70
|
return !val || val.trim() === "" || val.trim() === "无";
|
|
@@ -53,6 +76,69 @@ function wrap(val: string | undefined, fallback = "..."): string {
|
|
|
53
76
|
return val!.trim();
|
|
54
77
|
}
|
|
55
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
|
+
|
|
56
142
|
// ── Template Assemblers ──────────────────────────────────────
|
|
57
143
|
|
|
58
144
|
interface FeatFields {
|
|
@@ -62,6 +148,8 @@ interface FeatFields {
|
|
|
62
148
|
description: string;
|
|
63
149
|
painPoint: string;
|
|
64
150
|
testCmd: string;
|
|
151
|
+
acceptance?: string;
|
|
152
|
+
extra?: string;
|
|
65
153
|
}
|
|
66
154
|
|
|
67
155
|
function assembleFeatPrompt(f: FeatFields): string {
|
|
@@ -88,6 +176,7 @@ function assembleFeatPrompt(f: FeatFields): string {
|
|
|
88
176
|
if (!isEmpty(f.testCmd)) {
|
|
89
177
|
lines.push(`**验证**:运行 ${f.testCmd!.trim()} 确保无回归。`);
|
|
90
178
|
}
|
|
179
|
+
appendMeta(lines, f);
|
|
91
180
|
return lines.join("\n");
|
|
92
181
|
}
|
|
93
182
|
|
|
@@ -99,6 +188,8 @@ interface FixFields {
|
|
|
99
188
|
expected: string;
|
|
100
189
|
actualError: string;
|
|
101
190
|
testCmd: string;
|
|
191
|
+
acceptance?: string;
|
|
192
|
+
extra?: string;
|
|
102
193
|
}
|
|
103
194
|
|
|
104
195
|
function assembleFixPrompt(f: FixFields): string {
|
|
@@ -120,6 +211,7 @@ function assembleFixPrompt(f: FixFields): string {
|
|
|
120
211
|
if (!isEmpty(f.testCmd)) {
|
|
121
212
|
lines.push(`**验证**:运行 ${f.testCmd!.trim()} 确认修复。`);
|
|
122
213
|
}
|
|
214
|
+
appendMeta(lines, f);
|
|
123
215
|
return lines.join("\n");
|
|
124
216
|
}
|
|
125
217
|
|
|
@@ -129,6 +221,8 @@ interface DocFields {
|
|
|
129
221
|
keyInfo: string;
|
|
130
222
|
language: string;
|
|
131
223
|
existingMaterial: string;
|
|
224
|
+
acceptance?: string;
|
|
225
|
+
extra?: string;
|
|
132
226
|
}
|
|
133
227
|
|
|
134
228
|
function assembleDocPrompt(f: DocFields): string {
|
|
@@ -149,6 +243,7 @@ function assembleDocPrompt(f: DocFields): string {
|
|
|
149
243
|
lines.push("**输出格式**:Markdown 层级标题,必要时插入表格/列表。");
|
|
150
244
|
lines.push("**约束**:避免空洞词汇(如\"细致入微\"\"深入探究\");每段都应有实质信息;保持原意,不添加原文没有的事实。");
|
|
151
245
|
lines.push("**验证**:请先提供大纲,经我确认后再扩展。");
|
|
246
|
+
appendMeta(lines, f);
|
|
152
247
|
return lines.join("\n");
|
|
153
248
|
}
|
|
154
249
|
|
|
@@ -158,6 +253,8 @@ interface RefactorFields {
|
|
|
158
253
|
problems: string;
|
|
159
254
|
goal: string;
|
|
160
255
|
testCmd: string;
|
|
256
|
+
acceptance?: string;
|
|
257
|
+
extra?: string;
|
|
161
258
|
}
|
|
162
259
|
|
|
163
260
|
function assembleRefactorPrompt(f: RefactorFields): string {
|
|
@@ -180,6 +277,7 @@ function assembleRefactorPrompt(f: RefactorFields): string {
|
|
|
180
277
|
if (!isEmpty(f.testCmd)) {
|
|
181
278
|
lines.push(`**验证**:运行 ${f.testCmd!.trim()} 并确保全部通过。`);
|
|
182
279
|
}
|
|
280
|
+
appendMeta(lines, f);
|
|
183
281
|
return lines.join("\n");
|
|
184
282
|
}
|
|
185
283
|
|
|
@@ -189,6 +287,8 @@ interface TestFields {
|
|
|
189
287
|
coverage: string;
|
|
190
288
|
edgeCases: string;
|
|
191
289
|
testCmd: string;
|
|
290
|
+
acceptance?: string;
|
|
291
|
+
extra?: string;
|
|
192
292
|
}
|
|
193
293
|
|
|
194
294
|
function assembleTestPrompt(f: TestFields): string {
|
|
@@ -205,6 +305,7 @@ function assembleTestPrompt(f: TestFields): string {
|
|
|
205
305
|
if (!isEmpty(f.testCmd)) {
|
|
206
306
|
lines.push(`**验证**:运行 ${f.testCmd!.trim()} 并展示覆盖率报告。`);
|
|
207
307
|
}
|
|
308
|
+
appendMeta(lines, f);
|
|
208
309
|
return lines.join("\n");
|
|
209
310
|
}
|
|
210
311
|
|
|
@@ -214,6 +315,8 @@ interface ChoreFields {
|
|
|
214
315
|
envDesc: string;
|
|
215
316
|
targetVersion: string;
|
|
216
317
|
verifyCmd: string;
|
|
318
|
+
acceptance?: string;
|
|
319
|
+
extra?: string;
|
|
217
320
|
}
|
|
218
321
|
|
|
219
322
|
function assembleChorePrompt(f: ChoreFields): string {
|
|
@@ -235,6 +338,7 @@ function assembleChorePrompt(f: ChoreFields): string {
|
|
|
235
338
|
if (!isEmpty(f.verifyCmd)) {
|
|
236
339
|
lines.push(`**验证**:运行 ${f.verifyCmd!.trim()} 并展示结果。`);
|
|
237
340
|
}
|
|
341
|
+
appendMeta(lines, f);
|
|
238
342
|
return lines.join("\n");
|
|
239
343
|
}
|
|
240
344
|
|
|
@@ -244,6 +348,8 @@ interface PerfFields {
|
|
|
244
348
|
currentCost: string;
|
|
245
349
|
targetLatency: string;
|
|
246
350
|
benchCmd: string;
|
|
351
|
+
acceptance?: string;
|
|
352
|
+
extra?: string;
|
|
247
353
|
}
|
|
248
354
|
|
|
249
355
|
function assemblePerfPrompt(f: PerfFields): string {
|
|
@@ -263,6 +369,7 @@ function assemblePerfPrompt(f: PerfFields): string {
|
|
|
263
369
|
}
|
|
264
370
|
lines.push("**输出**:提供 before/after 性能对比表格。");
|
|
265
371
|
lines.push("**约束**:不要牺牲核心准确性;优先给出低风险改动;不为了微优化牺牲可读性。");
|
|
372
|
+
appendMeta(lines, f);
|
|
266
373
|
return lines.join("\n");
|
|
267
374
|
}
|
|
268
375
|
|
|
@@ -271,13 +378,15 @@ interface StyleFields {
|
|
|
271
378
|
description: string;
|
|
272
379
|
terms: string;
|
|
273
380
|
lintCmd: string;
|
|
381
|
+
acceptance?: string;
|
|
382
|
+
extra?: string;
|
|
274
383
|
}
|
|
275
384
|
|
|
276
385
|
function assembleStylePrompt(f: StyleFields): string {
|
|
277
386
|
const lines: string[] = [];
|
|
278
387
|
lines.push(`[style] 将以下内容调整为 ${wrap(f.targetStyle)}`);
|
|
279
388
|
lines.push("");
|
|
280
|
-
lines.push("
|
|
389
|
+
lines.push("**角色**:你是一位代码风格专家。");
|
|
281
390
|
lines.push(`**原文**:${wrap(f.description, "(见当前上下文)")}`);
|
|
282
391
|
lines.push("**任务**:");
|
|
283
392
|
lines.push("1. 保持原意和信息完整,仅改变表达风格/代码格式。");
|
|
@@ -291,12 +400,15 @@ function assembleStylePrompt(f: StyleFields): string {
|
|
|
291
400
|
if (!isEmpty(f.lintCmd)) {
|
|
292
401
|
lines.push(`**验证**:对代码运行 ${f.lintCmd!.trim()} 确保符合规范。`);
|
|
293
402
|
}
|
|
403
|
+
appendMeta(lines, f);
|
|
294
404
|
return lines.join("\n");
|
|
295
405
|
}
|
|
296
406
|
|
|
297
407
|
interface SecurityFields {
|
|
298
408
|
filePath: string;
|
|
299
409
|
focus: string;
|
|
410
|
+
acceptance?: string;
|
|
411
|
+
extra?: string;
|
|
300
412
|
}
|
|
301
413
|
|
|
302
414
|
function assembleSecurityPrompt(f: SecurityFields): string {
|
|
@@ -310,6 +422,7 @@ function assembleSecurityPrompt(f: SecurityFields): string {
|
|
|
310
422
|
lines.push("3. 只审查不修改,输出审查报告。");
|
|
311
423
|
lines.push("**硬性约束**:在隔离上下文中运行,不继承主 Agent 的记忆。");
|
|
312
424
|
lines.push("**输出**:Markdown 报告,每个问题包含严重级别、行号、风险描述、修复建议。");
|
|
425
|
+
appendMeta(lines, f);
|
|
313
426
|
return lines.join("\n");
|
|
314
427
|
}
|
|
315
428
|
|
|
@@ -317,6 +430,8 @@ interface ExplainFields {
|
|
|
317
430
|
concept: string;
|
|
318
431
|
audience: string;
|
|
319
432
|
depth: string;
|
|
433
|
+
acceptance?: string;
|
|
434
|
+
extra?: string;
|
|
320
435
|
}
|
|
321
436
|
|
|
322
437
|
function assembleExplainPrompt(f: ExplainFields): string {
|
|
@@ -332,6 +447,7 @@ function assembleExplainPrompt(f: ExplainFields): string {
|
|
|
332
447
|
lines.push("4. 如有常见误区,明确指出。");
|
|
333
448
|
lines.push("**输出格式**:Markdown,必要时插入图示描述。");
|
|
334
449
|
lines.push("**验证**:请先给出一句话总结,经我确认后再展开。");
|
|
450
|
+
appendMeta(lines, f);
|
|
335
451
|
return lines.join("\n");
|
|
336
452
|
}
|
|
337
453
|
|
|
@@ -339,6 +455,8 @@ interface CompareFields {
|
|
|
339
455
|
itemA: string;
|
|
340
456
|
itemB: string;
|
|
341
457
|
dimensions: string;
|
|
458
|
+
acceptance?: string;
|
|
459
|
+
extra?: string;
|
|
342
460
|
}
|
|
343
461
|
|
|
344
462
|
function assembleComparePrompt(f: CompareFields): string {
|
|
@@ -353,517 +471,333 @@ function assembleComparePrompt(f: CompareFields): string {
|
|
|
353
471
|
lines.push("3. 给出综合结论和建议。");
|
|
354
472
|
lines.push("**输出格式**:Markdown 表格 + 简短分析。");
|
|
355
473
|
lines.push("**约束**:客观中立,不偏袒任何一方,明确标注不确定的结论。");
|
|
474
|
+
appendMeta(lines, f);
|
|
356
475
|
return lines.join("\n");
|
|
357
476
|
}
|
|
358
477
|
|
|
359
|
-
// ──
|
|
360
|
-
|
|
361
|
-
const _fixGrillAgent = discoverAgents().find(a => a.name === "dev-fix-grill-agent")!;
|
|
362
|
-
const _docGrillAgent = discoverAgents().find(a => a.name === "dev-doc-grill-agent")!;
|
|
363
|
-
const _refactorGrillAgent = discoverAgents().find(a => a.name === "dev-refactor-grill-agent")!;
|
|
364
|
-
const _testGrillAgent = discoverAgents().find(a => a.name === "dev-test-grill-agent")!;
|
|
365
|
-
const _perfGrillAgent = discoverAgents().find(a => a.name === "dev-perf-grill-agent")!;
|
|
366
|
-
|
|
367
|
-
// ── Workflow configurations ──────────────────────────────────
|
|
368
|
-
|
|
369
|
-
const FEAT_WORKFLOW_STEPS: WorkflowStepDef[] = [
|
|
370
|
-
{
|
|
371
|
-
id: "planner",
|
|
372
|
-
label: "📋 生成实施计划",
|
|
373
|
-
type: "auto",
|
|
374
|
-
agentName: "planner",
|
|
375
|
-
timeoutMs: 900_000,
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
id: "worker-reviewer",
|
|
379
|
-
label: "🔧 实施代码 → 审查",
|
|
380
|
-
type: "loop-group",
|
|
381
|
-
loopAgentName: "worker",
|
|
382
|
-
reviewAgentName: "reviewer",
|
|
383
|
-
maxLoops: 3,
|
|
384
|
-
timeoutMs: 1_800_000,
|
|
385
|
-
reviewTimeoutMs: 900_000,
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
id: "trimmer-reviewer",
|
|
389
|
-
label: "✂️ 精简代码 → 审查",
|
|
390
|
-
type: "loop-group",
|
|
391
|
-
loopAgentName: "trimmer",
|
|
392
|
-
reviewAgentName: "reviewer",
|
|
393
|
-
maxLoops: 3,
|
|
394
|
-
timeoutMs: 1_200_000,
|
|
395
|
-
reviewTimeoutMs: 900_000,
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
id: "docWriter",
|
|
399
|
-
label: "📝 更新文档",
|
|
400
|
-
type: "confirm",
|
|
401
|
-
agentName: "docWriter",
|
|
402
|
-
timeoutMs: 600_000,
|
|
403
|
-
},
|
|
404
|
-
];
|
|
405
|
-
|
|
406
|
-
const FIX_WORKFLOW_STEPS: WorkflowStepDef[] = [
|
|
407
|
-
{
|
|
408
|
-
id: "planner",
|
|
409
|
-
label: "📋 分析根因并制定修复计划",
|
|
410
|
-
type: "auto",
|
|
411
|
-
agentName: "planner",
|
|
412
|
-
timeoutMs: 900_000,
|
|
413
|
-
},
|
|
414
|
-
{
|
|
415
|
-
id: "worker-reviewer",
|
|
416
|
-
label: "🔧 修复代码 → 审查",
|
|
417
|
-
type: "loop-group",
|
|
418
|
-
loopAgentName: "worker",
|
|
419
|
-
reviewAgentName: "reviewer",
|
|
420
|
-
maxLoops: 3,
|
|
421
|
-
timeoutMs: 1_800_000,
|
|
422
|
-
reviewTimeoutMs: 900_000,
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
id: "docWriter",
|
|
426
|
-
label: "📝 更新文档",
|
|
427
|
-
type: "confirm",
|
|
428
|
-
agentName: "docWriter",
|
|
429
|
-
timeoutMs: 600_000,
|
|
430
|
-
},
|
|
431
|
-
];
|
|
432
|
-
|
|
433
|
-
const REFACTOR_WORKFLOW_STEPS: WorkflowStepDef[] = [
|
|
434
|
-
{
|
|
435
|
-
id: "planner",
|
|
436
|
-
label: "📋 分析重构计划",
|
|
437
|
-
type: "auto",
|
|
438
|
-
agentName: "planner",
|
|
439
|
-
timeoutMs: 900_000,
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
id: "worker-reviewer",
|
|
443
|
-
label: "🔧 重构代码 → 审查",
|
|
444
|
-
type: "loop-group",
|
|
445
|
-
loopAgentName: "worker",
|
|
446
|
-
reviewAgentName: "reviewer",
|
|
447
|
-
maxLoops: 3,
|
|
448
|
-
timeoutMs: 1_800_000,
|
|
449
|
-
reviewTimeoutMs: 900_000,
|
|
450
|
-
},
|
|
451
|
-
{
|
|
452
|
-
id: "trimmer-reviewer",
|
|
453
|
-
label: "✂️ 精简代码 → 审查",
|
|
454
|
-
type: "loop-group",
|
|
455
|
-
loopAgentName: "trimmer",
|
|
456
|
-
reviewAgentName: "reviewer",
|
|
457
|
-
maxLoops: 3,
|
|
458
|
-
timeoutMs: 1_200_000,
|
|
459
|
-
reviewTimeoutMs: 900_000,
|
|
460
|
-
},
|
|
461
|
-
];
|
|
462
|
-
|
|
463
|
-
const PERF_WORKFLOW_STEPS: WorkflowStepDef[] = [
|
|
464
|
-
{
|
|
465
|
-
id: "planner",
|
|
466
|
-
label: "📋 分析性能问题并制定优化计划",
|
|
467
|
-
type: "auto",
|
|
468
|
-
agentName: "planner",
|
|
469
|
-
timeoutMs: 900_000,
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
id: "worker-reviewer",
|
|
473
|
-
label: "⚡ 优化代码 → 审查",
|
|
474
|
-
type: "loop-group",
|
|
475
|
-
loopAgentName: "worker",
|
|
476
|
-
reviewAgentName: "reviewer",
|
|
477
|
-
maxLoops: 3,
|
|
478
|
-
timeoutMs: 1_800_000,
|
|
479
|
-
reviewTimeoutMs: 900_000,
|
|
480
|
-
},
|
|
481
|
-
];
|
|
482
|
-
|
|
483
|
-
const TEST_WORKFLOW_STEPS: WorkflowStepDef[] = [
|
|
484
|
-
{
|
|
485
|
-
id: "planner",
|
|
486
|
-
label: "📋 分析测试计划",
|
|
487
|
-
type: "auto",
|
|
488
|
-
agentName: "planner",
|
|
489
|
-
timeoutMs: 900_000,
|
|
490
|
-
},
|
|
491
|
-
{
|
|
492
|
-
id: "worker-reviewer",
|
|
493
|
-
label: "🧪 编写测试 → 审查",
|
|
494
|
-
type: "loop-group",
|
|
495
|
-
loopAgentName: "worker",
|
|
496
|
-
reviewAgentName: "reviewer",
|
|
497
|
-
maxLoops: 3,
|
|
498
|
-
timeoutMs: 1_800_000,
|
|
499
|
-
reviewTimeoutMs: 900_000,
|
|
500
|
-
},
|
|
501
|
-
];
|
|
502
|
-
|
|
503
|
-
const DOC_WORKFLOW_STEPS: WorkflowStepDef[] = [
|
|
504
|
-
{
|
|
505
|
-
id: "planner",
|
|
506
|
-
label: "📋 分析文档需求",
|
|
507
|
-
type: "auto",
|
|
508
|
-
agentName: "planner",
|
|
509
|
-
timeoutMs: 900_000,
|
|
510
|
-
},
|
|
511
|
-
{
|
|
512
|
-
id: "docWriter",
|
|
513
|
-
label: "📝 撰写文档",
|
|
514
|
-
type: "auto",
|
|
515
|
-
agentName: "docWriter",
|
|
516
|
-
timeoutMs: 600_000,
|
|
517
|
-
},
|
|
518
|
-
];
|
|
519
|
-
|
|
520
|
-
const STYLE_WORKFLOW_STEPS: WorkflowStepDef[] = [
|
|
521
|
-
{
|
|
522
|
-
id: "trimmer-reviewer",
|
|
523
|
-
label: "✂️ 风格调整 → 审查",
|
|
524
|
-
type: "loop-group",
|
|
525
|
-
loopAgentName: "trimmer",
|
|
526
|
-
reviewAgentName: "reviewer",
|
|
527
|
-
maxLoops: 2,
|
|
528
|
-
timeoutMs: 1_200_000,
|
|
529
|
-
reviewTimeoutMs: 900_000,
|
|
530
|
-
},
|
|
531
|
-
];
|
|
532
|
-
|
|
533
|
-
const SECURITY_WORKFLOW_STEPS: WorkflowStepDef[] = [
|
|
534
|
-
{
|
|
535
|
-
id: "reviewer",
|
|
536
|
-
label: "🔒 安全审查",
|
|
537
|
-
type: "auto",
|
|
538
|
-
agentName: "reviewer",
|
|
539
|
-
timeoutMs: 900_000,
|
|
540
|
-
},
|
|
541
|
-
];
|
|
542
|
-
|
|
543
|
-
// ── Command runner ───────────────────────────────────────────
|
|
544
|
-
|
|
545
|
-
/** Format workflow steps into a readable list. */
|
|
546
|
-
function formatWorkflowSteps(steps: WorkflowStepDef[]): string {
|
|
547
|
-
return steps.map((s, i) => `${i + 1}. ${s.label}`).join("\n");
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* Prompt user to choose workflow mode and optionally customize sub-agent chain.
|
|
552
|
-
*
|
|
553
|
-
* Returns true if workflow was started and handled (caller should return immediately).
|
|
554
|
-
* Returns false if caller should fall through to direct prompt sending.
|
|
555
|
-
*/
|
|
556
|
-
async function promptWorkflowDecision(
|
|
557
|
-
ctx: ExtensionCommandContext,
|
|
558
|
-
pi: ExtensionAPI,
|
|
559
|
-
finalPrompt: string,
|
|
560
|
-
defaultSteps: WorkflowStepDef[],
|
|
561
|
-
): Promise<boolean> {
|
|
562
|
-
if (!defaultSteps || defaultSteps.length === 0) return false;
|
|
563
|
-
|
|
564
|
-
const choice = await uiSelect(
|
|
565
|
-
ctx,
|
|
566
|
-
"🚀 选择工作流模式",
|
|
567
|
-
[
|
|
568
|
-
"1. 使用默认链式子代理(推荐)",
|
|
569
|
-
"2. 自定义链式子代理",
|
|
570
|
-
"3. 退出工作流(直接发送 prompt 给主代理)",
|
|
571
|
-
],
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
if (!choice || choice.startsWith("3")) {
|
|
575
|
-
return false;
|
|
576
|
-
}
|
|
478
|
+
// ── 四段式收尾:验收标准 + 额外补充 ───────────────────────────
|
|
577
479
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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()}`);
|
|
582
485
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
let stepIdx = 0;
|
|
587
|
-
while (stepIdx >= 0 && stepIdx < defaultSteps.length) {
|
|
588
|
-
const step = defaultSteps[stepIdx]!;
|
|
589
|
-
|
|
590
|
-
// If this step was already added, show current config, else show default
|
|
591
|
-
const existingStep = customSteps.find(cs => cs.id === step.id);
|
|
592
|
-
const include = await uiConfirm(
|
|
593
|
-
ctx,
|
|
594
|
-
`📌 ${step.label}${existingStep ? ' (已添加)' : ''}`,
|
|
595
|
-
`类型: ${step.type}\n默认超时: ${(step.timeoutMs / 60000).toFixed(0)} 分钟`,
|
|
596
|
-
stepIdx > 0, // backable only after first step
|
|
597
|
-
);
|
|
598
|
-
if (include === "back") {
|
|
599
|
-
// Go back: remove the last added step
|
|
600
|
-
if (customSteps.length > 0) {
|
|
601
|
-
customSteps.pop();
|
|
602
|
-
stepIdx--;
|
|
603
|
-
continue;
|
|
604
|
-
}
|
|
605
|
-
return false; // nothing to go back to
|
|
606
|
-
}
|
|
607
|
-
if (include === undefined) return false; // Esc
|
|
608
|
-
|
|
609
|
-
if (!include) {
|
|
610
|
-
stepIdx++;
|
|
611
|
-
continue; // skip this step
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const existingTimeout = existingStep?.timeoutMs;
|
|
615
|
-
const timeoutStr = await uiInput(
|
|
616
|
-
ctx,
|
|
617
|
-
`⏱️ ${step.label} - 超时时间(分钟)`,
|
|
618
|
-
existingTimeout
|
|
619
|
-
? `当前: ${(existingTimeout / 60000).toFixed(0)} 分钟,留空保持`
|
|
620
|
-
: `留空保持默认 (${(step.timeoutMs / 60000).toFixed(0)} 分钟)`,
|
|
621
|
-
);
|
|
622
|
-
if (timeoutStr === undefined) { stepIdx++; continue; } // Esc on input = skip
|
|
623
|
-
|
|
624
|
-
if (existingStep) {
|
|
625
|
-
// Update existing step's timeout
|
|
626
|
-
existingStep.timeoutMs = timeoutStr ? parseInt(timeoutStr, 10) * 60 * 1000 || step.timeoutMs : step.timeoutMs;
|
|
627
|
-
} else {
|
|
628
|
-
customSteps.push({
|
|
629
|
-
...step,
|
|
630
|
-
timeoutMs: timeoutStr ? parseInt(timeoutStr, 10) * 60 * 1000 || step.timeoutMs : step.timeoutMs,
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
stepIdx++;
|
|
486
|
+
if (f.extra?.trim()) {
|
|
487
|
+
lines.push("");
|
|
488
|
+
lines.push(`**额外补充**:${f.extra.trim()}`);
|
|
634
489
|
}
|
|
490
|
+
}
|
|
635
491
|
|
|
636
|
-
|
|
637
|
-
|
|
492
|
+
// ── 默认字段填充:未提问/跳过的字段注入流畅默认值 ───────────────
|
|
493
|
+
|
|
494
|
+
const FIELD_DEFAULTS: Record<string, (d: ProjectDefaults) => Record<string, string>> = {
|
|
495
|
+
feat: (d) => ({
|
|
496
|
+
language: d.language || "项目使用的主流语言",
|
|
497
|
+
techStack: "项目当前技术栈",
|
|
498
|
+
module: "项目相关模块",
|
|
499
|
+
testCmd: d.testCmd,
|
|
500
|
+
}),
|
|
501
|
+
fix: (d) => ({
|
|
502
|
+
expected: "修复后行为符合预期",
|
|
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
|
+
}),
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
/** 注入未填字段的默认值;验收标准为空时使用项目常规验收。 */
|
|
550
|
+
function applyDefaults(answers: Record<string, string>, type: string, d: ProjectDefaults): void {
|
|
551
|
+
const injected = FIELD_DEFAULTS[type]?.(d) ?? {};
|
|
552
|
+
for (const [k, v] of Object.entries(injected)) {
|
|
553
|
+
if (!answers[k]?.trim()) answers[k] = v;
|
|
638
554
|
}
|
|
639
|
-
|
|
640
|
-
saveAnswerFile(ctx.cwd, finalPrompt);
|
|
641
|
-
await runWorkflow(ctx, pi, finalPrompt, { steps: customSteps }, "自定义");
|
|
642
|
-
return true;
|
|
555
|
+
if (!answers.acceptance?.trim()) answers.acceptance = defaultAcceptance(d);
|
|
643
556
|
}
|
|
644
557
|
|
|
645
|
-
|
|
558
|
+
/**
|
|
559
|
+
* Run a wizard: ask questions, assemble prompt, persist it, and send to the current agent.
|
|
560
|
+
*/
|
|
561
|
+
async function runWizard(
|
|
646
562
|
ctx: ExtensionCommandContext,
|
|
647
563
|
pi: ExtensionAPI,
|
|
648
564
|
type: string,
|
|
649
565
|
label: string,
|
|
650
|
-
questions:
|
|
566
|
+
questions: WizardQuestion[],
|
|
651
567
|
assembler: (answers: Record<string, string>) => string,
|
|
652
|
-
grillOptions?: GrillOptions,
|
|
653
|
-
workflowConfig?: { steps: WorkflowStepDef[] },
|
|
654
568
|
): Promise<void> {
|
|
655
569
|
const answers: Record<string, string> = {};
|
|
656
570
|
let idx = 0;
|
|
657
571
|
|
|
658
572
|
while (idx >= 0 && idx < questions.length) {
|
|
659
573
|
const q = questions[idx]!;
|
|
660
|
-
const existingVal = answers[q.
|
|
574
|
+
const existingVal = answers[q.keys[0]!];
|
|
661
575
|
const placeholder = existingVal
|
|
662
576
|
? `(之前: ${existingVal.slice(0, 60)}) ${q.placeholder}`
|
|
663
577
|
: q.placeholder;
|
|
664
578
|
const val = await ask(ctx, q.label, placeholder, true, existingVal || "");
|
|
665
579
|
if (val === undefined) {
|
|
580
|
+
// Esc → cancel whole wizard
|
|
666
581
|
return;
|
|
667
582
|
}
|
|
668
583
|
if (val === BACK_MARKER) {
|
|
584
|
+
// Go back to previous question
|
|
669
585
|
if (idx > 0) {
|
|
670
586
|
idx--;
|
|
671
587
|
continue;
|
|
672
588
|
}
|
|
589
|
+
// Already at first question → cancel
|
|
673
590
|
return;
|
|
674
591
|
}
|
|
675
|
-
answers
|
|
592
|
+
assignAnswers(answers, q, val);
|
|
676
593
|
idx++;
|
|
677
594
|
}
|
|
678
595
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
// ── Grill phase (if agentDef provided) ────────────────────
|
|
682
|
-
let finalPrompt = basePrompt;
|
|
683
|
-
if (grillOptions) {
|
|
684
|
-
const grillResult = await runGrillPhase(basePrompt, ctx, {
|
|
685
|
-
agentDef: grillOptions.agentDef,
|
|
686
|
-
title: grillOptions.title,
|
|
687
|
-
description: grillOptions.description,
|
|
688
|
-
questionTitle: grillOptions.questionTitle,
|
|
689
|
-
loaderLabel: grillOptions.loaderLabel,
|
|
690
|
-
});
|
|
691
|
-
if (grillResult.cancelled) {
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
finalPrompt = grillResult.enhancedPrompt;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// ── Workflow phase ───────────────────────────
|
|
698
|
-
if (workflowConfig && workflowConfig.steps.length > 0) {
|
|
699
|
-
const handled = await promptWorkflowDecision(ctx, pi, finalPrompt, workflowConfig.steps);
|
|
700
|
-
if (handled) return;
|
|
701
|
-
}
|
|
596
|
+
applyDefaults(answers, type, detectProjectDefaults(ctx.cwd));
|
|
597
|
+
const prompt = assembler(answers);
|
|
702
598
|
|
|
703
599
|
// ── Guard & persist before sending ──────────────────────
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
} else {
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
const answerPath = saveAnswerFile(ctx.cwd, finalPrompt);
|
|
600
|
+
const finalPrompt = prompt || recoverFromBackup(ctx.cwd) || "";
|
|
601
|
+
if (!finalPrompt) return;
|
|
602
|
+
|
|
603
|
+
saveAnswerFile(ctx.cwd, finalPrompt);
|
|
713
604
|
pi.sendUserMessage(finalPrompt, { deliverAs: "followUp" });
|
|
714
605
|
}
|
|
715
606
|
|
|
716
607
|
/**
|
|
717
|
-
* Run a wizard
|
|
608
|
+
* Run a wizard with an optional Grill phase, then send to the current agent.
|
|
718
609
|
*/
|
|
719
|
-
async function
|
|
610
|
+
async function runWizardWithGrill(
|
|
720
611
|
ctx: ExtensionCommandContext,
|
|
721
612
|
pi: ExtensionAPI,
|
|
722
613
|
type: string,
|
|
723
614
|
label: string,
|
|
724
|
-
questions:
|
|
615
|
+
questions: WizardQuestion[],
|
|
725
616
|
assembler: (answers: Record<string, string>) => string,
|
|
726
|
-
|
|
617
|
+
grillOptions?: GrillOptions,
|
|
727
618
|
): Promise<void> {
|
|
728
619
|
const answers: Record<string, string> = {};
|
|
729
620
|
let idx = 0;
|
|
730
621
|
|
|
731
622
|
while (idx >= 0 && idx < questions.length) {
|
|
732
623
|
const q = questions[idx]!;
|
|
733
|
-
const existingVal = answers[q.
|
|
624
|
+
const existingVal = answers[q.keys[0]!];
|
|
734
625
|
const placeholder = existingVal
|
|
735
626
|
? `(之前: ${existingVal.slice(0, 60)}) ${q.placeholder}`
|
|
736
627
|
: q.placeholder;
|
|
737
628
|
const val = await ask(ctx, q.label, placeholder, true, existingVal || "");
|
|
738
629
|
if (val === undefined) {
|
|
739
|
-
// Esc → cancel whole wizard
|
|
740
630
|
return;
|
|
741
631
|
}
|
|
742
632
|
if (val === BACK_MARKER) {
|
|
743
|
-
// Go back to previous question
|
|
744
633
|
if (idx > 0) {
|
|
745
634
|
idx--;
|
|
746
635
|
continue;
|
|
747
636
|
}
|
|
748
|
-
// Already at first question → cancel
|
|
749
637
|
return;
|
|
750
638
|
}
|
|
751
|
-
answers
|
|
639
|
+
assignAnswers(answers, q, val);
|
|
752
640
|
idx++;
|
|
753
641
|
}
|
|
754
642
|
|
|
755
|
-
|
|
643
|
+
applyDefaults(answers, type, detectProjectDefaults(ctx.cwd));
|
|
644
|
+
const basePrompt = assembler(answers);
|
|
756
645
|
|
|
757
|
-
// ──
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
646
|
+
// ── Grill phase (current agent) ─────────────────────────
|
|
647
|
+
let finalPrompt = basePrompt;
|
|
648
|
+
if (grillOptions) {
|
|
649
|
+
const grillResult = await runGrillPhase(basePrompt, ctx, pi, {
|
|
650
|
+
title: grillOptions.title,
|
|
651
|
+
description: grillOptions.description,
|
|
652
|
+
questionTitle: grillOptions.questionTitle,
|
|
653
|
+
});
|
|
654
|
+
if (grillResult.cancelled) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
finalPrompt = grillResult.enhancedPrompt;
|
|
761
658
|
}
|
|
762
659
|
|
|
763
|
-
//
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
660
|
+
// ── Guard & persist before sending ──────────────────────
|
|
661
|
+
if (!finalPrompt) {
|
|
662
|
+
const recovered = recoverFromBackup(ctx.cwd);
|
|
663
|
+
if (recovered) {
|
|
664
|
+
finalPrompt = recovered;
|
|
665
|
+
} else {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
saveAnswerFile(ctx.cwd, finalPrompt);
|
|
670
|
+
pi.sendUserMessage(finalPrompt, { deliverAs: "followUp" });
|
|
768
671
|
}
|
|
769
672
|
|
|
770
673
|
// ── Questions for each command ────────────────────────────────
|
|
674
|
+
// 每命令 2-5 问:核心字段 + 验收标准(可跳过,回车用默认)+ 额外补充(可跳过)。
|
|
771
675
|
|
|
772
|
-
const FEAT_QUESTIONS = [
|
|
773
|
-
{ label: "
|
|
774
|
-
{ label: "
|
|
775
|
-
{ label: "
|
|
776
|
-
{ label: "核心功能描述", placeholder: "用户可以通过邮箱+密码注册并登录", key: "description" },
|
|
777
|
-
{ label: "用户痛点/当前缺少", placeholder: "当前缺少用户认证系统,每次手动校验身份", key: "painPoint" },
|
|
778
|
-
{ label: "测试命令(可选)", placeholder: "如 npm test, cargo test, go test...", key: "testCmd" },
|
|
676
|
+
const FEAT_QUESTIONS: WizardQuestion[] = [
|
|
677
|
+
{ label: "核心功能描述(必填)", placeholder: "如 实现邮箱密码注册登录;模块/技术细节可在最后补充", keys: ["description"] },
|
|
678
|
+
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认:项目测试 + lint + pre-commit + CI 常规验收", keys: ["acceptance"] },
|
|
679
|
+
{ label: "额外补充(可跳过)", placeholder: "如 目标模块 src/auth/login.ts、技术栈 NestJS、当前痛点、测试命令等", keys: ["extra"] },
|
|
779
680
|
];
|
|
780
681
|
|
|
781
|
-
const FIX_QUESTIONS = [
|
|
782
|
-
{ label: "
|
|
783
|
-
{ label: "
|
|
784
|
-
{ label: "
|
|
785
|
-
{ label: "
|
|
786
|
-
{ label: "预期行为", placeholder: "应返回 200 和 token", key: "expected" },
|
|
787
|
-
{ label: "当前错误信息", placeholder: "Unauthorized (401) - 不符合预期的输出", key: "actualError" },
|
|
788
|
-
{ label: "测试命令(可选)", placeholder: "如 npm test, go test...", key: "testCmd" },
|
|
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"] },
|
|
789
687
|
];
|
|
790
688
|
|
|
791
|
-
const DOC_QUESTIONS = [
|
|
792
|
-
{ label: "
|
|
793
|
-
{ label: "
|
|
794
|
-
{ label: "
|
|
795
|
-
{ label: "示例语言", placeholder: "如 TypeScript, Python, curl...", key: "language" },
|
|
796
|
-
{ label: "已有材料(可选)", placeholder: "现有 README、笔记、文件路径等,留空则从零生成", key: "existingMaterial" },
|
|
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"] },
|
|
797
693
|
];
|
|
798
694
|
|
|
799
|
-
const REFACTOR_QUESTIONS = [
|
|
800
|
-
{ label: "文件路径", placeholder: "如 src/auth/login.ts",
|
|
801
|
-
{ label: "
|
|
802
|
-
{ label: "
|
|
803
|
-
{ label: "重构目标", placeholder: "如 可读性 / 可维护性 / 模块化", key: "goal" },
|
|
804
|
-
{ label: "测试命令(可选)", placeholder: "如 npm test, cargo test...", key: "testCmd" },
|
|
695
|
+
const REFACTOR_QUESTIONS: WizardQuestion[] = [
|
|
696
|
+
{ label: "文件路径", placeholder: "如 src/auth/login.ts(可标约行数)", keys: ["filePath"] },
|
|
697
|
+
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准", keys: ["acceptance"] },
|
|
698
|
+
{ label: "额外补充(可跳过)", placeholder: "如 具体问题、重构目标、测试命令", keys: ["extra"] },
|
|
805
699
|
];
|
|
806
700
|
|
|
807
|
-
const TEST_QUESTIONS = [
|
|
808
|
-
{ label: "文件路径", placeholder: "如 src/auth/login.ts",
|
|
809
|
-
{ label: "
|
|
810
|
-
{ label: "
|
|
811
|
-
{ label: "边界条件", placeholder: "如 null 值、空值、超时、幂等性、4xx/5xx 错误", key: "edgeCases" },
|
|
812
|
-
{ label: "测试命令(可选)", placeholder: "如 npm test -- --coverage", key: "testCmd" },
|
|
701
|
+
const TEST_QUESTIONS: WizardQuestion[] = [
|
|
702
|
+
{ label: "文件路径", placeholder: "如 src/auth/login.ts", keys: ["filePath"] },
|
|
703
|
+
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准(含覆盖率与边界条件)", keys: ["acceptance"] },
|
|
704
|
+
{ label: "额外补充(可跳过)", placeholder: "如 测试框架、目标覆盖率、重点边界条件、测试命令", keys: ["extra"] },
|
|
813
705
|
];
|
|
814
706
|
|
|
815
|
-
const CHORE_QUESTIONS = [
|
|
816
|
-
{ label: "配置文件/目标路径", placeholder: "如 package.json
|
|
817
|
-
{ label: "具体任务", placeholder: "如
|
|
818
|
-
{ label: "
|
|
819
|
-
{ label: "
|
|
820
|
-
{ label: "验证命令(可选)", placeholder: "如 npm run build, pnpm test...", key: "verifyCmd" },
|
|
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"] },
|
|
821
712
|
];
|
|
822
713
|
|
|
823
|
-
const PERF_QUESTIONS = [
|
|
824
|
-
{ label: "文件路径", placeholder: "如 src/services/query.ts",
|
|
825
|
-
{ label: "
|
|
826
|
-
{ label: "
|
|
827
|
-
{ label: "目标延迟", placeholder: "如 200ms", key: "targetLatency" },
|
|
828
|
-
{ label: "基准测试命令(可选)", placeholder: "如 npm run bench, go test -bench=.", key: "benchCmd" },
|
|
714
|
+
const PERF_QUESTIONS: WizardQuestion[] = [
|
|
715
|
+
{ label: "文件路径", placeholder: "如 src/services/query.ts", keys: ["filePath"] },
|
|
716
|
+
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准(含基准对比)", keys: ["acceptance"] },
|
|
717
|
+
{ label: "额外补充(可跳过)", placeholder: "如 瓶颈、当前耗时/成本、目标延迟、基准命令", keys: ["extra"] },
|
|
829
718
|
];
|
|
830
719
|
|
|
831
|
-
const STYLE_QUESTIONS = [
|
|
832
|
-
{ label: "
|
|
833
|
-
{ label: "
|
|
834
|
-
{ label: "
|
|
835
|
-
{ label: "Linter/格式化命令(可选)", placeholder: "如 npx prettier --check, npm run lint", key: "lintCmd" },
|
|
720
|
+
const STYLE_QUESTIONS: WizardQuestion[] = [
|
|
721
|
+
{ label: "待调整内容", placeholder: "如 调整以下函数的命名风格,或粘贴文本", keys: ["description"] },
|
|
722
|
+
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认验收标准(含 lint)", keys: ["acceptance"] },
|
|
723
|
+
{ label: "额外补充(可跳过)", placeholder: "如 目标风格、术语统一、校验命令", keys: ["extra"] },
|
|
836
724
|
];
|
|
837
725
|
|
|
838
|
-
const SECURITY_QUESTIONS = [
|
|
839
|
-
{ label: "文件路径", placeholder: "如 src/api/auth.ts",
|
|
840
|
-
{ label: "
|
|
726
|
+
const SECURITY_QUESTIONS: WizardQuestion[] = [
|
|
727
|
+
{ label: "文件路径", placeholder: "如 src/api/auth.ts", keys: ["filePath"] },
|
|
728
|
+
{ label: "验收标准(可跳过)", placeholder: "直接回车将使用默认安全审查清单", keys: ["acceptance"] },
|
|
841
729
|
];
|
|
842
730
|
|
|
843
|
-
const EXPLAIN_QUESTIONS = [
|
|
844
|
-
{ label: "概念名称", placeholder: "如 React Server Component
|
|
845
|
-
{ label: "
|
|
846
|
-
{ label: "
|
|
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"] },
|
|
847
735
|
];
|
|
848
736
|
|
|
849
|
-
const COMPARE_QUESTIONS = [
|
|
850
|
-
{ label: "对比对象 A", placeholder: "如 Vue 3
|
|
851
|
-
{ label: "对比对象 B", placeholder: "如 React 18
|
|
852
|
-
{ label: "
|
|
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"] },
|
|
853
742
|
];
|
|
854
743
|
|
|
855
744
|
// ── Extension ────────────────────────────────────────────────
|
|
856
745
|
|
|
857
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
|
+
|
|
858
792
|
// ── /dev-feat ──────────────────────────────────────────────
|
|
859
793
|
pi.registerCommand("dev-feat", {
|
|
860
|
-
description: "(prompt wizard) 新功能/创意生成 — 支持设计方案追问完善 (Grill)
|
|
794
|
+
description: "(prompt wizard) 新功能/创意生成 — 支持设计方案追问完善 (Grill)",
|
|
861
795
|
handler: async (_args, ctx) => {
|
|
862
796
|
const answers: Record<string, string> = {};
|
|
863
797
|
let featIdx = 0;
|
|
864
798
|
while (featIdx >= 0 && featIdx < FEAT_QUESTIONS.length) {
|
|
865
799
|
const q = FEAT_QUESTIONS[featIdx]!;
|
|
866
|
-
const existingVal = answers[q.
|
|
800
|
+
const existingVal = answers[q.keys[0]!];
|
|
867
801
|
const placeholder = existingVal
|
|
868
802
|
? `(之前: ${existingVal.slice(0, 60)}) ${q.placeholder}`
|
|
869
803
|
: q.placeholder;
|
|
@@ -878,33 +812,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
878
812
|
}
|
|
879
813
|
return;
|
|
880
814
|
}
|
|
881
|
-
answers
|
|
815
|
+
assignAnswers(answers, q, val);
|
|
882
816
|
featIdx++;
|
|
883
817
|
}
|
|
884
818
|
|
|
819
|
+
applyDefaults(answers, "feat", detectProjectDefaults(ctx.cwd));
|
|
885
820
|
const basePrompt = assembleFeatPrompt(answers as FeatFields);
|
|
886
821
|
|
|
887
|
-
const grillResult = await runGrillPhase(basePrompt, ctx
|
|
822
|
+
const grillResult = await runGrillPhase(basePrompt, ctx, pi, {
|
|
823
|
+
title: "🔍 设计方案追问完善",
|
|
824
|
+
description: "AI 会通过系统性追问帮你打磨方案:从术语精确化到边界条件验证,确保架构决策的每个分支都经过推敲。",
|
|
825
|
+
questionTitle: "设计方案追问完善",
|
|
826
|
+
});
|
|
888
827
|
if (grillResult.cancelled) {
|
|
889
828
|
return;
|
|
890
829
|
}
|
|
891
|
-
const finalPrompt = grillResult.enhancedPrompt;
|
|
830
|
+
const finalPrompt = grillResult.enhancedPrompt || basePrompt;
|
|
892
831
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
if (handled) return;
|
|
896
|
-
}
|
|
832
|
+
// ── PRD phase (current agent) ──────────────────────────
|
|
833
|
+
await runPRDPhase(finalPrompt, (answers as FeatFields).module || "feature", pi, ctx);
|
|
897
834
|
|
|
898
|
-
|
|
899
|
-
const recovered = recoverFromBackup(ctx.cwd);
|
|
900
|
-
if (recovered) {
|
|
901
|
-
const answerPath = saveAnswerFile(ctx.cwd, recovered);
|
|
902
|
-
pi.sendUserMessage(recovered, { deliverAs: "followUp" });
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
return;
|
|
906
|
-
}
|
|
907
|
-
const answerPath = saveAnswerFile(ctx.cwd, finalPrompt);
|
|
835
|
+
saveAnswerFile(ctx.cwd, finalPrompt);
|
|
908
836
|
pi.sendUserMessage(finalPrompt, { deliverAs: "followUp" });
|
|
909
837
|
},
|
|
910
838
|
});
|
|
@@ -917,13 +845,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
917
845
|
ctx, pi, "fix", "问题排查/错误修正",
|
|
918
846
|
FIX_QUESTIONS, assembleFixPrompt,
|
|
919
847
|
{
|
|
920
|
-
agentDef: _fixGrillAgent,
|
|
921
848
|
title: "🐛 Bug 根因追问",
|
|
922
|
-
description: "AI
|
|
849
|
+
description: "AI 会通过系统性追问帮你精准定位根因:从复现条件到根本原因推理,再到修复方案验证和回归风险评估。",
|
|
923
850
|
questionTitle: "Bug 根因分析",
|
|
924
|
-
loaderLabel: "🧠 AI 正在分析代码并生成根因追问问题...",
|
|
925
851
|
},
|
|
926
|
-
{ steps: FIX_WORKFLOW_STEPS },
|
|
927
852
|
);
|
|
928
853
|
},
|
|
929
854
|
});
|
|
@@ -936,13 +861,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
936
861
|
ctx, pi, "doc", "文档生成/总结",
|
|
937
862
|
DOC_QUESTIONS, assembleDocPrompt,
|
|
938
863
|
{
|
|
939
|
-
agentDef: _docGrillAgent,
|
|
940
864
|
title: "📄 文档大纲追问完善",
|
|
941
865
|
description: "AI 会通过追问帮你完善文档大纲:从受众定位到结构安排,确认术语一致性和示例覆盖范围。",
|
|
942
866
|
questionTitle: "文档大纲追问完善",
|
|
943
|
-
loaderLabel: "🧠 AI 正在分析并生成文档大纲追问问题...",
|
|
944
867
|
},
|
|
945
|
-
{ steps: DOC_WORKFLOW_STEPS },
|
|
946
868
|
);
|
|
947
869
|
},
|
|
948
870
|
});
|
|
@@ -955,13 +877,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
955
877
|
ctx, pi, "refactor", "重构/优化",
|
|
956
878
|
REFACTOR_QUESTIONS, assembleRefactorPrompt,
|
|
957
879
|
{
|
|
958
|
-
agentDef: _refactorGrillAgent,
|
|
959
880
|
title: "🔧 重构方案追问",
|
|
960
881
|
description: "AI 会通过追问帮你识别隐藏耦合风险:从模块边界到 API 兼容性,验证行为保持和迁移路径安全性。",
|
|
961
882
|
questionTitle: "重构方案追问",
|
|
962
|
-
loaderLabel: "🧠 AI 正在分析代码并生成重构追问问题...",
|
|
963
883
|
},
|
|
964
|
-
{ steps: REFACTOR_WORKFLOW_STEPS },
|
|
965
884
|
);
|
|
966
885
|
},
|
|
967
886
|
});
|
|
@@ -974,13 +893,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
974
893
|
ctx, pi, "test", "测试用例/评估",
|
|
975
894
|
TEST_QUESTIONS, assembleTestPrompt,
|
|
976
895
|
{
|
|
977
|
-
agentDef: _testGrillAgent,
|
|
978
896
|
title: "🧪 测试策略追问",
|
|
979
897
|
description: "AI 会通过追问帮你发现测试缺口:从覆盖维度到边界条件,验证模拟策略和测试隔离是否到位。",
|
|
980
898
|
questionTitle: "测试策略追问",
|
|
981
|
-
loaderLabel: "🧠 AI 正在分析并生成测试追问问题...",
|
|
982
899
|
},
|
|
983
|
-
{ steps: TEST_WORKFLOW_STEPS },
|
|
984
900
|
);
|
|
985
901
|
},
|
|
986
902
|
});
|
|
@@ -1001,13 +917,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1001
917
|
ctx, pi, "perf", "性能优化",
|
|
1002
918
|
PERF_QUESTIONS, assemblePerfPrompt,
|
|
1003
919
|
{
|
|
1004
|
-
agentDef: _perfGrillAgent,
|
|
1005
920
|
title: "⚡ 性能优化方案追问",
|
|
1006
921
|
description: "AI 会通过追问帮你验证瓶颈判断和优化方向:从基准测试方法到潜在回归风险,确保方案合理性。",
|
|
1007
922
|
questionTitle: "性能优化方案追问",
|
|
1008
|
-
loaderLabel: "🧠 AI 正在分析并生成性能优化追问问题...",
|
|
1009
923
|
},
|
|
1010
|
-
{ steps: PERF_WORKFLOW_STEPS },
|
|
1011
924
|
);
|
|
1012
925
|
},
|
|
1013
926
|
});
|
|
@@ -1016,7 +929,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1016
929
|
pi.registerCommand("dev-style", {
|
|
1017
930
|
description: "(prompt wizard) 风格/格式调整 — 交互填写后发送优化提示词给主代理",
|
|
1018
931
|
handler: async (_args, ctx) => {
|
|
1019
|
-
await runWizard(ctx, pi, "style", "风格/格式调整", STYLE_QUESTIONS, assembleStylePrompt
|
|
932
|
+
await runWizard(ctx, pi, "style", "风格/格式调整", STYLE_QUESTIONS, assembleStylePrompt);
|
|
1020
933
|
},
|
|
1021
934
|
});
|
|
1022
935
|
|
|
@@ -1024,7 +937,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1024
937
|
pi.registerCommand("dev-security", {
|
|
1025
938
|
description: "(prompt wizard) 安全审查 — 交互填写后发送优化提示词给主代理",
|
|
1026
939
|
handler: async (_args, ctx) => {
|
|
1027
|
-
await runWizard(ctx, pi, "security", "安全审查", SECURITY_QUESTIONS, assembleSecurityPrompt
|
|
940
|
+
await runWizard(ctx, pi, "security", "安全审查", SECURITY_QUESTIONS, assembleSecurityPrompt);
|
|
1028
941
|
},
|
|
1029
942
|
});
|
|
1030
943
|
|
|
@@ -1043,16 +956,4 @@ export default function (pi: ExtensionAPI) {
|
|
|
1043
956
|
await runWizard(ctx, pi, "compare", "对比评估", COMPARE_QUESTIONS, assembleComparePrompt);
|
|
1044
957
|
},
|
|
1045
958
|
});
|
|
1046
|
-
|
|
1047
|
-
// ── /dev-workflow-continue — 恢复中断的工作流 ─────────────
|
|
1048
|
-
pi.registerCommand("dev-workflow-continue", {
|
|
1049
|
-
description: "恢复上次中断的自动化工作流(从 checkpoint 继续)",
|
|
1050
|
-
handler: async (_args, ctx) => {
|
|
1051
|
-
const cp = loadCheckpointFromFile(ctx.cwd);
|
|
1052
|
-
if (!cp) {
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
await runWorkflow(ctx, pi, cp.prompt, { steps: FEAT_WORKFLOW_STEPS }, "恢复");
|
|
1056
|
-
},
|
|
1057
|
-
});
|
|
1058
959
|
}
|