@ghyper9023/pi-dev-workflow 0.3.1 → 0.4.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.
@@ -24,17 +24,22 @@
24
24
  */
25
25
 
26
26
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
27
- import { runGrillPhase, runPRDPhase, type AgentDef, type GrillOptions } from "./grill-me-agent";
27
+ 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";
30
+ import { uiSelect, uiConfirm, uiInput, BACK_MARKER } from "./ui-helpers";
28
31
 
29
32
  // ── Helpers ──────────────────────────────────────────────────
30
33
 
31
- /** Ask a single question. Returns `undefined` on cancel (Esc). */
34
+ /** Ask a single question with proper wrapping. Returns `undefined` on cancel (Esc), or BACK_MARKER for back. */
32
35
  async function ask(
33
36
  ctx: ExtensionCommandContext,
34
37
  label: string,
35
38
  placeholder: string,
39
+ backable = false,
40
+ initialValue = "",
36
41
  ): Promise<string | undefined> {
37
- return ctx.ui.input(label, { placeholder, required: false });
42
+ return uiInput(ctx, label, placeholder, false, backable, initialValue);
38
43
  }
39
44
 
40
45
  /** Check if a field value is empty or explicitly "无". */
@@ -351,170 +356,284 @@ function assembleComparePrompt(f: CompareFields): string {
351
356
  return lines.join("\n");
352
357
  }
353
358
 
354
- // ── Specialized Agent Definitions for Grill ─────────────────
355
-
356
- /** Bug fix root cause analysis & reproduction review. */
357
- const FIX_GRILL_AGENT_DEF: AgentDef = {
358
- name: "fix-grill-agent",
359
- description: "Bug fix review agent challenges the developer on understanding the bug root cause",
360
- tools: ["read", "bash"],
361
- systemPrompt: [
362
- "You are an expert debugger reviewing a bug fix plan. Interview the developer relentlessly.",
363
- "",
364
- "## Rules",
365
- "- For EACH question, provide recommended answer OPTIONS (a/b/c format)",
366
- "- Focus on: reproduction steps, environment conditions, input variations, error messages, logs, attempted fixes",
367
- "- Push for precise root cause identification — ask 'why' at least 3 levels deep",
368
- "- Check if the developer has considered: edge cases in inputs, race conditions, state leakage, timeout, memory",
369
- "- Verify the proposed fix addresses the root cause, not just the symptom",
370
- "- If the codebase has logs/metrics available, suggest checking specific patterns",
371
- "- Stress-test regression risk: could the fix break other parts of the system?",
372
- "- Explore the codebase (use read/bash tools) when a question can be answered by looking at existing code",
373
- "",
374
- "## Output format",
375
- "Output ALL questions in ONE JSON response. No preamble or explanation.",
376
- 'Only output the JSON object: { "questions": [{ "id": 1, "question": "...", "options": ["..."] }] }',
377
- "",
378
- "## Quantity",
379
- "Ask 5-15 questions — enough to thoroughly understand the root cause before committing to a fix.",
380
- "",
381
- "## Language",
382
- "Questions and options should be in the same language as the bug report (default: Chinese).",
383
- ].join("\n"),
384
- timeoutMs: 300_000,
385
- };
386
-
387
- /** Document outline & structure review. */
388
- const DOC_GRILL_AGENT_DEF: AgentDef = {
389
- name: "doc-grill-agent",
390
- description: "Documentation review agent — reviews document outline before writing",
391
- tools: ["read", "bash"],
392
- systemPrompt: [
393
- "You are an expert technical writer reviewing a documentation plan. Interview the developer.",
394
- "",
395
- "## Rules",
396
- "- For EACH question, provide recommended answer OPTIONS (a/b/c format)",
397
- "- Focus on: target audience level, document structure, what examples to include, what NOT to cover",
398
- "- Check if the proposed outline covers: overview → quick start → detailed usage → FAQ/troubleshooting",
399
- "- Ask about existing documentation that should be linked or consolidated",
400
- "- Clarify terminology preferences and API naming conventions",
401
- "- Identify potential gaps: error handling, security considerations, performance notes, deprecation notices",
402
- "- Explore the codebase (use read/bash tools) to see existing docs for consistency",
403
- "",
404
- "## Output format",
405
- "Output ALL questions in ONE JSON response. No preamble or explanation.",
406
- 'Only output the JSON object: { "questions": [{ "id": 1, "question": "...", "options": ["..."] }] }',
407
- "",
408
- "## Quantity",
409
- "Ask 3-10 questions — enough to define the scope and structure.",
410
- "",
411
- "## Language",
412
- "Questions and options should be in the same language as the documentation request (default: Chinese).",
413
- ].join("\n"),
414
- timeoutMs: 300_000,
415
- };
416
-
417
- /** Refactoring plan review. */
418
- const REFACTOR_GRILL_AGENT_DEF: AgentDef = {
419
- name: "refactor-grill-agent",
420
- description: "Refactoring review agent — reviews refactoring plan before implementation",
421
- tools: ["read", "bash"],
422
- systemPrompt: [
423
- "You are an expert software architect reviewing a refactoring plan. Interview the developer.",
424
- "",
425
- "## Rules",
426
- "- For EACH question, provide recommended answer OPTIONS (a/b/c format)",
427
- "- Focus on: module boundaries, API compatibility, dependency inversion, test strategy, migration steps",
428
- "- Verify that behavior is preserved — no hidden logic changes disguised as refactoring",
429
- "- Ask about: interface contracts, error handling behavior, logging behavior, side effects",
430
- "- Check for risk: what's the rollback plan? Can we refactor incrementally?",
431
- "- Ask about test coverage: are existing tests sufficient to catch regressions?",
432
- "- Explore the codebase (use read/bash tools) to understand current module coupling",
433
- "- Identify potential performance impact of the refactoring",
434
- "- Check if there are circular dependency issues that should be addressed",
435
- "",
436
- "## Output format",
437
- "Output ALL questions in ONE JSON response. No preamble or explanation.",
438
- 'Only output the JSON object: { "questions": [{ "id": 1, "question": "...", "options": ["..."] }] }',
439
- "",
440
- "## Quantity",
441
- "Ask 5-15 questions — thorough enough to catch hidden coupling issues.",
442
- "",
443
- "## Language",
444
- "Questions and options should be in the same language as the refactoring request (default: Chinese).",
445
- ].join("\n"),
446
- timeoutMs: 300_000,
447
- };
448
-
449
- /** Test plan coverage review. */
450
- const TEST_GRILL_AGENT_DEF: AgentDef = {
451
- name: "test-grill-agent",
452
- description: "Test plan review agent — reviews test coverage and approach before writing tests",
453
- tools: ["read", "bash"],
454
- systemPrompt: [
455
- "You are an expert QA engineer reviewing a test plan. Interview the developer.",
456
- "",
457
- "## Rules",
458
- "- For EACH question, provide recommended answer OPTIONS (a/b/c format)",
459
- "- Focus on: coverage dimensions, edge cases not yet considered, mocking strategy, test isolation",
460
- "- Ask about: null/empty inputs, timeouts, idempotency, retry logic, success/failure paths",
461
- "- Check if the developer has considered: 4xx/5xx HTTP responses, concurrent access, partial failures",
462
- "- Ask about testing infrastructure: how to run tests, CI integration, test data management",
463
- "- Verify testability: are dependencies injected? Can we mock external services?",
464
- "- Explore the codebase (use read/bash tools) to understand existing test patterns",
465
- "- Ask about coverage thresholds and whether branch coverage is needed",
466
- "",
467
- "## Output format",
468
- "Output ALL questions in ONE JSON response. No preamble or explanation.",
469
- 'Only output the JSON object: { "questions": [{ "id": 1, "question": "...", "options": ["..."] }] }',
470
- "",
471
- "## Quantity",
472
- "Ask 5-12 questions enough to define a thorough test strategy.",
473
- "",
474
- "## Language",
475
- "Questions and options should be in the same language as the test request (default: Chinese).",
476
- ].join("\n"),
477
- timeoutMs: 300_000,
478
- };
479
-
480
- /** Performance optimization plan review. */
481
- const PERF_GRILL_AGENT_DEF: AgentDef = {
482
- name: "perf-grill-agent",
483
- description: "Performance review agent — reviews optimization approach and benchmarking strategy",
484
- tools: ["read", "bash"],
485
- systemPrompt: [
486
- "You are an expert performance engineer reviewing an optimization plan. Interview the developer.",
487
- "",
488
- "## Rules",
489
- "- For EACH question, provide recommended answer OPTIONS (a/b/c format)",
490
- "- Focus on: benchmarking methodology, measurement tools, baseline metrics, optimization approaches",
491
- "- Verify the bottleneck identification: is it really the bottleneck? What's the evidence?",
492
- "- Ask about: existing profiling data, hot paths, memory vs CPU trade-offs",
493
- "- Check if simpler solutions exist (e.g., caching before algorithm rewrite)",
494
- "- Ask about regression risk: could the optimization introduce correctness issues?",
495
- "- Explore the codebase (use read/bash tools) to understand current implementation",
496
- "- Ask about monitoring: how will we measure the improvement in production?",
497
- "- Challenge assumptions: is this optimization premature? What's the user-facing impact?",
498
- "",
499
- "## Output format",
500
- "Output ALL questions in ONE JSON response. No preamble or explanation.",
501
- 'Only output the JSON object: { "questions": [{ "id": 1, "question": "...", "options": ["..."] }] }',
502
- "",
503
- "## Quantity",
504
- "Ask 5-12 questions — enough to validate the approach before implementation.",
505
- "",
506
- "## Language",
507
- "Questions and options should be in the same language as the optimization request (default: Chinese).",
508
- ].join("\n"),
509
- timeoutMs: 300_000,
510
- };
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: 900_000,
385
+ },
386
+ {
387
+ id: "trimmer-reviewer",
388
+ label: "✂️ 精简代码 → 审查",
389
+ type: "loop-group",
390
+ loopAgentName: "trimmer",
391
+ reviewAgentName: "reviewer",
392
+ maxLoops: 3,
393
+ timeoutMs: 300_000,
394
+ },
395
+ {
396
+ id: "docWriter",
397
+ label: "📝 更新文档",
398
+ type: "confirm",
399
+ agentName: "docWriter",
400
+ timeoutMs: 300_000,
401
+ },
402
+ ];
403
+
404
+ const FIX_WORKFLOW_STEPS: WorkflowStepDef[] = [
405
+ {
406
+ id: "planner",
407
+ label: "📋 分析根因并制定修复计划",
408
+ type: "auto",
409
+ agentName: "planner",
410
+ timeoutMs: 900_000,
411
+ },
412
+ {
413
+ id: "worker-reviewer",
414
+ label: "🔧 修复代码 审查",
415
+ type: "loop-group",
416
+ loopAgentName: "worker",
417
+ reviewAgentName: "reviewer",
418
+ maxLoops: 3,
419
+ timeoutMs: 900_000,
420
+ },
421
+ {
422
+ id: "docWriter",
423
+ label: "📝 更新文档",
424
+ type: "confirm",
425
+ agentName: "docWriter",
426
+ timeoutMs: 300_000,
427
+ },
428
+ ];
429
+
430
+ const REFACTOR_WORKFLOW_STEPS: WorkflowStepDef[] = [
431
+ {
432
+ id: "planner",
433
+ label: "📋 分析重构计划",
434
+ type: "auto",
435
+ agentName: "planner",
436
+ timeoutMs: 900_000,
437
+ },
438
+ {
439
+ id: "worker-reviewer",
440
+ label: "🔧 重构代码 → 审查",
441
+ type: "loop-group",
442
+ loopAgentName: "worker",
443
+ reviewAgentName: "reviewer",
444
+ maxLoops: 3,
445
+ timeoutMs: 900_000,
446
+ },
447
+ {
448
+ id: "trimmer-reviewer",
449
+ label: "✂️ 精简代码 审查",
450
+ type: "loop-group",
451
+ loopAgentName: "trimmer",
452
+ reviewAgentName: "reviewer",
453
+ maxLoops: 3,
454
+ timeoutMs: 300_000,
455
+ },
456
+ ];
457
+
458
+ const PERF_WORKFLOW_STEPS: WorkflowStepDef[] = [
459
+ {
460
+ id: "planner",
461
+ label: "📋 分析性能问题并制定优化计划",
462
+ type: "auto",
463
+ agentName: "planner",
464
+ timeoutMs: 900_000,
465
+ },
466
+ {
467
+ id: "worker-reviewer",
468
+ label: "⚡ 优化代码 审查",
469
+ type: "loop-group",
470
+ loopAgentName: "worker",
471
+ reviewAgentName: "reviewer",
472
+ maxLoops: 3,
473
+ timeoutMs: 900_000,
474
+ },
475
+ ];
476
+
477
+ const TEST_WORKFLOW_STEPS: WorkflowStepDef[] = [
478
+ {
479
+ id: "planner",
480
+ label: "📋 分析测试计划",
481
+ type: "auto",
482
+ agentName: "planner",
483
+ timeoutMs: 900_000,
484
+ },
485
+ {
486
+ id: "worker-reviewer",
487
+ label: "🧪 编写测试 → 审查",
488
+ type: "loop-group",
489
+ loopAgentName: "worker",
490
+ reviewAgentName: "reviewer",
491
+ maxLoops: 3,
492
+ timeoutMs: 900_000,
493
+ },
494
+ ];
495
+
496
+ const DOC_WORKFLOW_STEPS: WorkflowStepDef[] = [
497
+ {
498
+ id: "planner",
499
+ label: "📋 分析文档需求",
500
+ type: "auto",
501
+ agentName: "planner",
502
+ timeoutMs: 900_000,
503
+ },
504
+ {
505
+ id: "docWriter",
506
+ label: "📝 撰写文档",
507
+ type: "auto",
508
+ agentName: "docWriter",
509
+ timeoutMs: 300_000,
510
+ },
511
+ ];
512
+
513
+ const STYLE_WORKFLOW_STEPS: WorkflowStepDef[] = [
514
+ {
515
+ id: "trimmer-reviewer",
516
+ label: "✂️ 风格调整 → 审查",
517
+ type: "loop-group",
518
+ loopAgentName: "trimmer",
519
+ reviewAgentName: "reviewer",
520
+ maxLoops: 2,
521
+ timeoutMs: 300_000,
522
+ },
523
+ ];
524
+
525
+ const SECURITY_WORKFLOW_STEPS: WorkflowStepDef[] = [
526
+ {
527
+ id: "reviewer",
528
+ label: "🔒 安全审查",
529
+ type: "auto",
530
+ agentName: "reviewer",
531
+ timeoutMs: 300_000,
532
+ },
533
+ ];
511
534
 
512
535
  // ── Command runner ───────────────────────────────────────────
513
536
 
537
+ /** Format workflow steps into a readable list. */
538
+ function formatWorkflowSteps(steps: WorkflowStepDef[]): string {
539
+ return steps.map((s, i) => `${i + 1}. ${s.label}`).join("\n");
540
+ }
541
+
514
542
  /**
515
- * Run a wizard with an optional Grill phase:
516
- * Wizard → Assemble → (Grill?) → Send
543
+ * Prompt user to choose workflow mode and optionally customize sub-agent chain.
544
+ *
545
+ * Returns true if workflow was started and handled (caller should return immediately).
546
+ * Returns false if caller should fall through to direct prompt sending.
517
547
  */
548
+ async function promptWorkflowDecision(
549
+ ctx: ExtensionCommandContext,
550
+ pi: ExtensionAPI,
551
+ finalPrompt: string,
552
+ defaultSteps: WorkflowStepDef[],
553
+ ): Promise<boolean> {
554
+ if (!defaultSteps || defaultSteps.length === 0) return false;
555
+
556
+ const choice = await uiSelect(
557
+ ctx,
558
+ "🚀 选择工作流模式",
559
+ [
560
+ "1. 使用默认链式子代理(推荐)",
561
+ "2. 自定义链式子代理",
562
+ "3. 退出工作流(直接发送 prompt 给主代理)",
563
+ ],
564
+ );
565
+
566
+ if (!choice || choice.startsWith("3")) {
567
+ return false;
568
+ }
569
+
570
+ if (choice.startsWith("1")) {
571
+ saveAnswerFile(ctx.cwd, finalPrompt);
572
+ await runWorkflow(ctx, pi, finalPrompt, { steps: defaultSteps }, "快速链式");
573
+ return true;
574
+ }
575
+
576
+ // ── Custom mode: let user pick steps and set timeouts (with back support) ──
577
+ const customSteps: WorkflowStepDef[] = [];
578
+ let stepIdx = 0;
579
+ while (stepIdx >= 0 && stepIdx < defaultSteps.length) {
580
+ const step = defaultSteps[stepIdx]!;
581
+
582
+ // If this step was already added, show current config, else show default
583
+ const existingStep = customSteps.find(cs => cs.id === step.id);
584
+ const include = await uiConfirm(
585
+ ctx,
586
+ `📌 ${step.label}${existingStep ? ' (已添加)' : ''}`,
587
+ `类型: ${step.type}\n默认超时: ${(step.timeoutMs / 60000).toFixed(0)} 分钟`,
588
+ stepIdx > 0, // backable only after first step
589
+ );
590
+ if (include === "back") {
591
+ // Go back: remove the last added step
592
+ if (customSteps.length > 0) {
593
+ customSteps.pop();
594
+ stepIdx--;
595
+ continue;
596
+ }
597
+ return false; // nothing to go back to
598
+ }
599
+ if (include === undefined) return false; // Esc
600
+
601
+ if (!include) {
602
+ stepIdx++;
603
+ continue; // skip this step
604
+ }
605
+
606
+ const existingTimeout = existingStep?.timeoutMs;
607
+ const timeoutStr = await uiInput(
608
+ ctx,
609
+ `⏱️ ${step.label} - 超时时间(分钟)`,
610
+ existingTimeout
611
+ ? `当前: ${(existingTimeout / 60000).toFixed(0)} 分钟,留空保持`
612
+ : `留空保持默认 (${(step.timeoutMs / 60000).toFixed(0)} 分钟)`,
613
+ );
614
+ if (timeoutStr === undefined) { stepIdx++; continue; } // Esc on input = skip
615
+
616
+ if (existingStep) {
617
+ // Update existing step's timeout
618
+ existingStep.timeoutMs = timeoutStr ? parseInt(timeoutStr, 10) * 60 * 1000 || step.timeoutMs : step.timeoutMs;
619
+ } else {
620
+ customSteps.push({
621
+ ...step,
622
+ timeoutMs: timeoutStr ? parseInt(timeoutStr, 10) * 60 * 1000 || step.timeoutMs : step.timeoutMs,
623
+ });
624
+ }
625
+ stepIdx++;
626
+ }
627
+
628
+ if (customSteps.length === 0) {
629
+ return false;
630
+ }
631
+
632
+ saveAnswerFile(ctx.cwd, finalPrompt);
633
+ await runWorkflow(ctx, pi, finalPrompt, { steps: customSteps }, "自定义");
634
+ return true;
635
+ }
636
+
518
637
  async function runWizardWithGrill(
519
638
  ctx: ExtensionCommandContext,
520
639
  pi: ExtensionAPI,
@@ -523,17 +642,30 @@ async function runWizardWithGrill(
523
642
  questions: Array<{ label: string; placeholder: string; key: string }>,
524
643
  assembler: (answers: Record<string, string>) => string,
525
644
  grillOptions?: GrillOptions,
645
+ workflowConfig?: { steps: WorkflowStepDef[] },
526
646
  ): Promise<void> {
527
- ctx.ui.notify(`📋 /dev-${type} — ${label},请逐项填写以下信息(留空跳过对应段落,Esc 取消)`, "info");
528
-
529
647
  const answers: Record<string, string> = {};
530
- for (const q of questions) {
531
- const val = await ask(ctx, q.label, q.placeholder);
648
+ let idx = 0;
649
+
650
+ while (idx >= 0 && idx < questions.length) {
651
+ const q = questions[idx]!;
652
+ const existingVal = answers[q.key];
653
+ const placeholder = existingVal
654
+ ? `(之前: ${existingVal.slice(0, 60)}) ${q.placeholder}`
655
+ : q.placeholder;
656
+ const val = await ask(ctx, q.label, placeholder, true, existingVal || "");
532
657
  if (val === undefined) {
533
- ctx.ui.notify("❌ 已取消", "warning");
658
+ return;
659
+ }
660
+ if (val === BACK_MARKER) {
661
+ if (idx > 0) {
662
+ idx--;
663
+ continue;
664
+ }
534
665
  return;
535
666
  }
536
667
  answers[q.key] = val;
668
+ idx++;
537
669
  }
538
670
 
539
671
  const basePrompt = assembler(answers);
@@ -549,25 +681,32 @@ async function runWizardWithGrill(
549
681
  loaderLabel: grillOptions.loaderLabel,
550
682
  });
551
683
  if (grillResult.cancelled) {
552
- ctx.ui.notify("❌ 操作已取消", "warning");
553
684
  return;
554
685
  }
555
686
  finalPrompt = grillResult.enhancedPrompt;
556
687
  }
557
688
 
558
- ctx.ui.notify(`✅ 提示词已组装完成,正在发送给主代理...`, "success");
559
- pi.sendUserMessage(finalPrompt);
560
- ctx.ui.notify(`📝 /dev-${type} 提示词已投递,主代理正在处理`, "info");
689
+ // ── Workflow phase ───────────────────────────
690
+ if (workflowConfig && workflowConfig.steps.length > 0) {
691
+ const handled = await promptWorkflowDecision(ctx, pi, finalPrompt, workflowConfig.steps);
692
+ if (handled) return;
693
+ }
694
+
695
+ // ── Guard & persist before sending ──────────────────────
696
+ if (!finalPrompt) {
697
+ const recovered = recoverFromBackup(ctx.cwd);
698
+ if (recovered) {
699
+ finalPrompt = recovered;
700
+ } else {
701
+ return;
702
+ }
703
+ }
704
+ const answerPath = saveAnswerFile(ctx.cwd, finalPrompt);
705
+ pi.sendUserMessage(finalPrompt, { deliverAs: "followUp" });
561
706
  }
562
707
 
563
708
  /**
564
709
  * Run a wizard: ask questions, assemble prompt, send to agent.
565
- * @param ctx Command context
566
- * @param pi Extension API (for sendUserMessage)
567
- * @param type Display name of the prompt type (e.g. "feat")
568
- * @param label Friendly label (e.g. "新功能/创意生成")
569
- * @param questions Array of [label, placeholder] pairs
570
- * @param assembler Function that takes answers and returns the prompt string
571
710
  */
572
711
  async function runWizard(
573
712
  ctx: ExtensionCommandContext,
@@ -576,28 +715,48 @@ async function runWizard(
576
715
  label: string,
577
716
  questions: Array<{ label: string; placeholder: string; key: string }>,
578
717
  assembler: (answers: Record<string, string>) => string,
718
+ workflowConfig?: { steps: WorkflowStepDef[] },
579
719
  ): Promise<void> {
580
- ctx.ui.notify(`📋 /dev-${type} — ${label},请逐项填写以下信息(留空跳过对应段落,Esc 取消)`, "info");
581
-
582
720
  const answers: Record<string, string> = {};
583
-
584
- for (const q of questions) {
585
- const val = await ask(ctx, q.label, q.placeholder);
721
+ let idx = 0;
722
+
723
+ while (idx >= 0 && idx < questions.length) {
724
+ const q = questions[idx]!;
725
+ const existingVal = answers[q.key];
726
+ const placeholder = existingVal
727
+ ? `(之前: ${existingVal.slice(0, 60)}) ${q.placeholder}`
728
+ : q.placeholder;
729
+ const val = await ask(ctx, q.label, placeholder, true, existingVal || "");
586
730
  if (val === undefined) {
587
- ctx.ui.notify("❌ 已取消", "warning");
731
+ // Esc → cancel whole wizard
732
+ return;
733
+ }
734
+ if (val === BACK_MARKER) {
735
+ // Go back to previous question
736
+ if (idx > 0) {
737
+ idx--;
738
+ continue;
739
+ }
740
+ // Already at first question → cancel
588
741
  return;
589
742
  }
590
743
  answers[q.key] = val;
744
+ idx++;
591
745
  }
592
746
 
593
747
  const prompt = assembler(answers);
594
- ctx.ui.notify(`✅ 提示词已组装完成,正在发送给主代理...`, "success");
595
748
 
596
- // Send the assembled prompt to the main agent
597
- pi.sendUserMessage(prompt);
749
+ // ── Workflow phase ───────────────────────────
750
+ if (workflowConfig && workflowConfig.steps.length > 0) {
751
+ const handled = await promptWorkflowDecision(ctx, pi, prompt, workflowConfig.steps);
752
+ if (handled) return;
753
+ }
598
754
 
599
- // Also show the prompt in a notification for reference
600
- ctx.ui.notify(`📝 /dev-${type} 提示词已投递,主代理正在处理`, "info");
755
+ // Persist prompt before sending
756
+ saveAnswerFile(ctx.cwd, prompt);
757
+
758
+ // Send the assembled prompt to the main agent
759
+ pi.sendUserMessage(prompt, { deliverAs: "followUp" });
601
760
  }
602
761
 
603
762
  // ── Questions for each command ────────────────────────────────
@@ -690,45 +849,55 @@ const COMPARE_QUESTIONS = [
690
849
  export default function (pi: ExtensionAPI) {
691
850
  // ── /dev-feat ──────────────────────────────────────────────
692
851
  pi.registerCommand("dev-feat", {
693
- description: "(prompt wizard) 新功能/创意生成 — 支持设计评审 (Grill) + PRD 生成",
852
+ description: "(prompt wizard) 新功能/创意生成 — 支持设计评审 (Grill) + 自动化工作流",
694
853
  handler: async (_args, ctx) => {
695
- // ── Phase 1: Wizard ──────────────────────────────────
696
- ctx.ui.notify("📋 /dev-feat — 新功能/创意生成,请逐项填写以下信息(留空跳过对应段落,Esc 取消)", "info");
697
-
698
854
  const answers: Record<string, string> = {};
699
- for (const q of FEAT_QUESTIONS) {
700
- const val = await ask(ctx, q.label, q.placeholder);
855
+ let featIdx = 0;
856
+ while (featIdx >= 0 && featIdx < FEAT_QUESTIONS.length) {
857
+ const q = FEAT_QUESTIONS[featIdx]!;
858
+ const existingVal = answers[q.key];
859
+ const placeholder = existingVal
860
+ ? `(之前: ${existingVal.slice(0, 60)}) ${q.placeholder}`
861
+ : q.placeholder;
862
+ const val = await ask(ctx, q.label, placeholder, true, existingVal || "");
701
863
  if (val === undefined) {
702
- ctx.ui.notify("❌ 已取消", "warning");
864
+ return;
865
+ }
866
+ if (val === BACK_MARKER) {
867
+ if (featIdx > 0) {
868
+ featIdx--;
869
+ continue;
870
+ }
703
871
  return;
704
872
  }
705
873
  answers[q.key] = val;
874
+ featIdx++;
706
875
  }
707
876
 
708
- // ── Phase 2: Assemble base prompt ───────────────────
709
877
  const basePrompt = assembleFeatPrompt(answers as FeatFields);
710
- ctx.ui.notify(`✅ 基本信息已收集,共 ${FEAT_QUESTIONS.length} 项`, "success");
711
878
 
712
- // ── Phase 3: Grill (设计评审) ───────────────────────
713
879
  const grillResult = await runGrillPhase(basePrompt, ctx);
714
880
  if (grillResult.cancelled) {
715
- ctx.ui.notify("❌ 操作已取消", "warning");
716
881
  return;
717
882
  }
718
883
  const finalPrompt = grillResult.enhancedPrompt;
719
884
 
720
- // ── Phase 4: Send to main agent ─────────────────────
721
- ctx.ui.notify(`✅ 提示词已组装完成,正在发送给主代理...`, "success");
722
- pi.sendUserMessage(finalPrompt);
723
- ctx.ui.notify(`📝 /dev-feat 提示词已投递,主代理正在处理`, "info");
724
-
725
- // ── Phase 5: Wait for agent to finish ───────────────
726
- await ctx.waitForIdle();
885
+ if (FEAT_WORKFLOW_STEPS.length > 0) {
886
+ const handled = await promptWorkflowDecision(ctx, pi, finalPrompt, FEAT_WORKFLOW_STEPS);
887
+ if (handled) return;
888
+ }
727
889
 
728
- // ── Phase 6: PRD generation ─────────────────────────
729
- const moduleHint = (answers as FeatFields).module || "feature";
730
- const grillContext = finalPrompt;
731
- await runPRDPhase(grillContext, moduleHint, pi, ctx);
890
+ if (!finalPrompt) {
891
+ const recovered = recoverFromBackup(ctx.cwd);
892
+ if (recovered) {
893
+ const answerPath = saveAnswerFile(ctx.cwd, recovered);
894
+ pi.sendUserMessage(recovered, { deliverAs: "followUp" });
895
+ return;
896
+ }
897
+ return;
898
+ }
899
+ const answerPath = saveAnswerFile(ctx.cwd, finalPrompt);
900
+ pi.sendUserMessage(finalPrompt, { deliverAs: "followUp" });
732
901
  },
733
902
  });
734
903
 
@@ -740,12 +909,13 @@ export default function (pi: ExtensionAPI) {
740
909
  ctx, pi, "fix", "问题排查/错误修正",
741
910
  FIX_QUESTIONS, assembleFixPrompt,
742
911
  {
743
- agentDef: FIX_GRILL_AGENT_DEF,
912
+ agentDef: _fixGrillAgent,
744
913
  title: "🐛 Bug 根因分析评审",
745
- description: "是否进入 Bug 根因分析评审 (Grill) 模式?\nAI 会从复现条件、根因推理、修复方案、回归风险等维度挑战你的理解。",
914
+ description: "AI 会从复现条件、根因推理、修复方案、回归风险等维度挑战你的理解。",
746
915
  questionTitle: "Bug 根因分析",
747
916
  loaderLabel: "🧠 AI 正在分析代码并生成根因评审问题...",
748
917
  },
918
+ { steps: FIX_WORKFLOW_STEPS },
749
919
  );
750
920
  },
751
921
  });
@@ -758,12 +928,13 @@ export default function (pi: ExtensionAPI) {
758
928
  ctx, pi, "doc", "文档生成/总结",
759
929
  DOC_QUESTIONS, assembleDocPrompt,
760
930
  {
761
- agentDef: DOC_GRILL_AGENT_DEF,
931
+ agentDef: _docGrillAgent,
762
932
  title: "📄 文档大纲评审",
763
- description: "是否进入文档大纲评审 (Grill) 模式?\nAI 会从受众定位、结构安排、示例选择等维度审视你的文档计划。",
933
+ description: "AI 会从受众定位、结构安排、示例选择等维度审视你的文档计划。",
764
934
  questionTitle: "文档大纲评审",
765
935
  loaderLabel: "🧠 AI 正在分析并生成文档大纲评审问题...",
766
936
  },
937
+ { steps: DOC_WORKFLOW_STEPS },
767
938
  );
768
939
  },
769
940
  });
@@ -776,12 +947,13 @@ export default function (pi: ExtensionAPI) {
776
947
  ctx, pi, "refactor", "重构/优化",
777
948
  REFACTOR_QUESTIONS, assembleRefactorPrompt,
778
949
  {
779
- agentDef: REFACTOR_GRILL_AGENT_DEF,
950
+ agentDef: _refactorGrillAgent,
780
951
  title: "🔧 重构方案评审",
781
- description: "是否进入重构方案评审 (Grill) 模式?\nAI 会从模块边界、API 兼容性、测试策略、迁移风险等维度审视你的重构计划。",
952
+ description: "AI 会从模块边界、API 兼容性、测试策略、迁移风险等维度审视你的重构计划。",
782
953
  questionTitle: "重构方案评审",
783
954
  loaderLabel: "🧠 AI 正在分析代码并生成重构评审问题...",
784
955
  },
956
+ { steps: REFACTOR_WORKFLOW_STEPS },
785
957
  );
786
958
  },
787
959
  });
@@ -794,12 +966,13 @@ export default function (pi: ExtensionAPI) {
794
966
  ctx, pi, "test", "测试用例/评估",
795
967
  TEST_QUESTIONS, assembleTestPrompt,
796
968
  {
797
- agentDef: TEST_GRILL_AGENT_DEF,
969
+ agentDef: _testGrillAgent,
798
970
  title: "🧪 测试计划评审",
799
- description: "是否进入测试计划评审 (Grill) 模式?\nAI 会从覆盖维度、边界条件、模拟策略等角度审视你的测试方案。",
971
+ description: "AI 会从覆盖维度、边界条件、模拟策略等角度审视你的测试方案。",
800
972
  questionTitle: "测试计划评审",
801
973
  loaderLabel: "🧠 AI 正在分析并生成测试评审问题...",
802
974
  },
975
+ { steps: TEST_WORKFLOW_STEPS },
803
976
  );
804
977
  },
805
978
  });
@@ -820,12 +993,13 @@ export default function (pi: ExtensionAPI) {
820
993
  ctx, pi, "perf", "性能优化",
821
994
  PERF_QUESTIONS, assemblePerfPrompt,
822
995
  {
823
- agentDef: PERF_GRILL_AGENT_DEF,
996
+ agentDef: _perfGrillAgent,
824
997
  title: "⚡ 性能优化方案评审",
825
- description: "是否进入性能优化方案评审 (Grill) 模式?\nAI 会从基准测试方法、优化方向、回归风险等维度审视你的方案。",
998
+ description: "AI 会从基准测试方法、优化方向、回归风险等维度审视你的方案。",
826
999
  questionTitle: "性能优化方案评审",
827
1000
  loaderLabel: "🧠 AI 正在分析并生成性能优化评审问题...",
828
1001
  },
1002
+ { steps: PERF_WORKFLOW_STEPS },
829
1003
  );
830
1004
  },
831
1005
  });
@@ -834,7 +1008,7 @@ export default function (pi: ExtensionAPI) {
834
1008
  pi.registerCommand("dev-style", {
835
1009
  description: "(prompt wizard) 风格/格式调整 — 交互填写后发送优化提示词给主代理",
836
1010
  handler: async (_args, ctx) => {
837
- await runWizard(ctx, pi, "style", "风格/格式调整", STYLE_QUESTIONS, assembleStylePrompt);
1011
+ await runWizard(ctx, pi, "style", "风格/格式调整", STYLE_QUESTIONS, assembleStylePrompt, { steps: STYLE_WORKFLOW_STEPS });
838
1012
  },
839
1013
  });
840
1014
 
@@ -842,7 +1016,7 @@ export default function (pi: ExtensionAPI) {
842
1016
  pi.registerCommand("dev-security", {
843
1017
  description: "(prompt wizard) 安全审查 — 交互填写后发送优化提示词给主代理",
844
1018
  handler: async (_args, ctx) => {
845
- await runWizard(ctx, pi, "security", "安全审查", SECURITY_QUESTIONS, assembleSecurityPrompt);
1019
+ await runWizard(ctx, pi, "security", "安全审查", SECURITY_QUESTIONS, assembleSecurityPrompt, { steps: SECURITY_WORKFLOW_STEPS });
846
1020
  },
847
1021
  });
848
1022
 
@@ -861,4 +1035,16 @@ export default function (pi: ExtensionAPI) {
861
1035
  await runWizard(ctx, pi, "compare", "对比评估", COMPARE_QUESTIONS, assembleComparePrompt);
862
1036
  },
863
1037
  });
1038
+
1039
+ // ── /dev-workflow-continue — 恢复中断的工作流 ─────────────
1040
+ pi.registerCommand("dev-workflow-continue", {
1041
+ description: "恢复上次中断的自动化工作流(从 checkpoint 继续)",
1042
+ handler: async (_args, ctx) => {
1043
+ const cp = loadCheckpointFromFile(ctx.cwd);
1044
+ if (!cp) {
1045
+ return;
1046
+ }
1047
+ await runWorkflow(ctx, pi, cp.prompt, { steps: FEAT_WORKFLOW_STEPS }, "恢复");
1048
+ },
1049
+ });
864
1050
  }