@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.
Files changed (37) hide show
  1. package/.github/workflows/release.yml +56 -0
  2. package/.pre-commit-config.yaml +75 -0
  3. package/.version/RELEASE-v0.7.0.md +85 -0
  4. package/README.md +80 -228
  5. package/extensions/dev-prompts.ts +351 -450
  6. package/extensions/git-commands.ts +62 -171
  7. package/extensions/grill-me-agent.ts +98 -137
  8. package/extensions/session-utils.ts +169 -0
  9. package/extensions/ui-helpers.ts +2 -780
  10. package/package.json +2 -2
  11. package/prompts/APPEND_SYSTEM.md +63 -59
  12. package/skills/review-html/SKILL.md +1 -1
  13. package/tests/test-no-subagents.mjs +146 -0
  14. package/.doc/AGENT-FRONTMATTER-REFERENCE.md +0 -198
  15. package/agents/git-agent.md +0 -44
  16. package/agents/grill/dev-doc-grill-agent.md +0 -42
  17. package/agents/grill/dev-fix-grill-agent.md +0 -44
  18. package/agents/grill/dev-grill-agent.md +0 -40
  19. package/agents/grill/dev-perf-grill-agent.md +0 -45
  20. package/agents/grill/dev-prd-agent.md +0 -60
  21. package/agents/grill/dev-refactor-grill-agent.md +0 -46
  22. package/agents/grill/dev-test-grill-agent.md +0 -45
  23. package/agents/review-agent.md +0 -53
  24. package/agents/workflow/docWriter-agent.md +0 -53
  25. package/agents/workflow/planner-agent.md +0 -131
  26. package/agents/workflow/reviewer-agent.md +0 -128
  27. package/agents/workflow/trimmer-agent.md +0 -78
  28. package/agents/workflow/worker-agent.md +0 -70
  29. package/extensions/sub-agents.ts +0 -954
  30. package/extensions/workflow-engine.ts +0 -2005
  31. package/tests/test-grill-json-fix.mjs +0 -243
  32. package/tests/test-loopcount-timeout-fix.mjs +0 -336
  33. package/tests/test-output-directory-structure.mjs +0 -177
  34. package/tests/test-save-answer-file-workflow.mjs +0 -187
  35. package/tests/test-workflow-config.mjs +0 -244
  36. package/tests/test-workflow-engine-bugs.mjs +0 -908
  37. 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 main agent.
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 { discoverAgents } from "./sub-agents";
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
- // ── Specialized Agent Definitions (loaded from agents/grill/ directory) ──
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
- if (choice.startsWith("1")) {
579
- saveAnswerFile(ctx.cwd, finalPrompt);
580
- await runWorkflow(ctx, pi, finalPrompt, { steps: defaultSteps }, "快速链式");
581
- return true;
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
- // ── Custom mode: let user pick steps and set timeouts (with back support) ──
585
- const customSteps: WorkflowStepDef[] = [];
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
- if (customSteps.length === 0) {
637
- return false;
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
- async function runWizardWithGrill(
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: Array<{ label: string; placeholder: string; key: string }>,
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.key];
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[q.key] = val;
592
+ assignAnswers(answers, q, val);
676
593
  idx++;
677
594
  }
678
595
 
679
- const basePrompt = assembler(answers);
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
- if (!finalPrompt) {
705
- const recovered = recoverFromBackup(ctx.cwd);
706
- if (recovered) {
707
- finalPrompt = recovered;
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: ask questions, assemble prompt, send to agent.
608
+ * Run a wizard with an optional Grill phase, then send to the current agent.
718
609
  */
719
- async function runWizard(
610
+ async function runWizardWithGrill(
720
611
  ctx: ExtensionCommandContext,
721
612
  pi: ExtensionAPI,
722
613
  type: string,
723
614
  label: string,
724
- questions: Array<{ label: string; placeholder: string; key: string }>,
615
+ questions: WizardQuestion[],
725
616
  assembler: (answers: Record<string, string>) => string,
726
- workflowConfig?: { steps: WorkflowStepDef[] },
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.key];
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[q.key] = val;
639
+ assignAnswers(answers, q, val);
752
640
  idx++;
753
641
  }
754
642
 
755
- const prompt = assembler(answers);
643
+ applyDefaults(answers, type, detectProjectDefaults(ctx.cwd));
644
+ const basePrompt = assembler(answers);
756
645
 
757
- // ── Workflow phase ───────────────────────────
758
- if (workflowConfig && workflowConfig.steps.length > 0) {
759
- const handled = await promptWorkflowDecision(ctx, pi, prompt, workflowConfig.steps);
760
- if (handled) return;
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
- // Persist prompt before sending
764
- saveAnswerFile(ctx.cwd, prompt);
765
-
766
- // Send the assembled prompt to the main agent
767
- pi.sendUserMessage(prompt, { deliverAs: "followUp" });
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: "编程语言/框架", placeholder: "如 TypeScript, Python, Rust...", key: "language" },
774
- { label: "技术栈", placeholder: "如 NestJS + Prisma, React + Express...", key: "techStack" },
775
- { label: "目标模块/文件名", placeholder: "如 src/auth/login.ts", key: "module" },
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: "文件路径", placeholder: "如 src/auth/login.ts", key: "filePath" },
783
- { label: "行号(可选)", placeholder: "如 42,留空则扫描整个文件", key: "lineNumber" },
784
- { label: "Bug 描述", placeholder: "登录接口在密码正确时返回 401", key: "bugDesc" },
785
- { label: "输入/现象", placeholder: "输入正确邮箱和密码,返回 401 错误", key: "inputDesc" },
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: "模块/API/doc 名称", placeholder: "如 AuthService, REST API v2...", key: "moduleName" },
793
- { label: "目标受众", placeholder: "如 小白 / 前端开发者 / 架构师", key: "audience" },
794
- { label: "关键信息点", placeholder: "他们需要了解如何使用认证接口", key: "keyInfo" },
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", key: "filePath" },
801
- { label: "代码行数(可选)", placeholder: "如 200 行", key: "lineCount" },
802
- { label: "具体问题", placeholder: "如 重复逻辑、耦合度高、可读性差", key: "problems" },
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", key: "filePath" },
809
- { label: "测试框架", placeholder: "如 Jest / Vitest / pytest / Go test", key: "framework" },
810
- { label: "目标覆盖率", placeholder: "如 90,留空默认 90%", key: "coverage" },
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, .github/workflows/ci.yml", key: "configPath" },
817
- { label: "具体任务", placeholder: "如 更新依赖、修改构建脚本、调整 CI 配置", key: "task" },
818
- { label: "当前环境描述", placeholder: "如 Node 18, pnpm 8", key: "envDesc" },
819
- { label: "目标版本", placeholder: "如 Node 20, pnpm 9", key: "targetVersion" },
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", key: "filePath" },
825
- { label: "瓶颈描述", placeholder: "如 数据库查询延迟过高、内存泄漏", key: "bottleneck" },
826
- { label: "当前执行耗时", placeholder: "如 5 秒 / 成本 $0.02/次", key: "currentCost" },
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: "目标风格", placeholder: "如 正式商务 / 幽默 / 简洁要点式 / Prettier 规范", key: "targetStyle" },
833
- { label: "待调整内容描述", placeholder: "如 以下函数需要调整命名风格,或粘贴文本", key: "description" },
834
- { label: "术语统一(可选)", placeholder: "如 API → 接口, user → 用户", key: "terms" },
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", key: "filePath" },
840
- { label: "审查重点(可选)", placeholder: "如 认证边界、注入漏洞、敏感数据暴露、CSRF/CORS、权限校验", key: "focus" },
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, HTTP/3", key: "concept" },
845
- { label: "目标受众", placeholder: "如 小白 / 开发者 / 架构师", key: "audience" },
846
- { label: "理解深度(可选)", placeholder: "如 基础 / 进阶,留空默认基础", key: "depth" },
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, Next.js, PostgreSQL...", key: "itemA" },
851
- { label: "对比对象 B", placeholder: "如 React 18, Nuxt 3, MySQL...", key: "itemB" },
852
- { label: "评估维度(可选)", placeholder: "如 性能、生态、学习曲线、社区支持", key: "dimensions" },
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.key];
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[q.key] = val;
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
- if (FEAT_WORKFLOW_STEPS.length > 0) {
894
- const handled = await promptWorkflowDecision(ctx, pi, finalPrompt, FEAT_WORKFLOW_STEPS);
895
- if (handled) return;
896
- }
832
+ // ── PRD phase (current agent) ──────────────────────────
833
+ await runPRDPhase(finalPrompt, (answers as FeatFields).module || "feature", pi, ctx);
897
834
 
898
- if (!finalPrompt) {
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, { steps: STYLE_WORKFLOW_STEPS });
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, { steps: SECURITY_WORKFLOW_STEPS });
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
  }