@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.
- package/README.md +171 -29
- package/agents/grill/dev-doc-grill-agent.md +34 -0
- package/agents/grill/dev-fix-grill-agent.md +35 -0
- package/agents/grill/dev-grill-agent.md +33 -0
- package/agents/grill/dev-perf-grill-agent.md +36 -0
- package/agents/grill/dev-prd-agent.md +53 -0
- package/agents/grill/dev-refactor-grill-agent.md +36 -0
- package/agents/grill/dev-test-grill-agent.md +35 -0
- package/agents/review-agent.md +5 -5
- package/agents/workflow/docWriter-agent.md +29 -0
- package/agents/workflow/planner-agent.md +80 -0
- package/agents/workflow/reviewer-agent.md +44 -0
- package/agents/workflow/trimmer-agent.md +34 -0
- package/agents/workflow/worker-agent.md +29 -0
- package/extensions/dev-prompts.ts +408 -222
- package/extensions/git-commands.ts +3 -13
- package/extensions/grill-me-agent.ts +277 -150
- package/extensions/sub-agents.ts +53 -23
- package/extensions/ui-helpers.ts +1030 -0
- package/extensions/workflow-engine.ts +1715 -0
- package/package.json +1 -1
- package/skills/review-html/SKILL.md +2 -2
- package/skills/to-prd/SKILL.md +1 -1
- package/tests/test-grill-json-fix.mjs +243 -0
- package/tests/test-output-directory-structure.mjs +177 -0
- package/tests/test-save-answer-file-workflow.mjs +187 -0
- package/tests/test-workflow-config.mjs +244 -0
- package/tests/test-workflow-engine.mjs +518 -0
|
@@ -24,17 +24,22 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
27
|
-
import { runGrillPhase, runPRDPhase,
|
|
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
|
|
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
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
"
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
"",
|
|
374
|
-
"
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
"",
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
"",
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
"
|
|
403
|
-
"",
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
"
|
|
410
|
-
"",
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
"
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
"-
|
|
435
|
-
"",
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
"
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
"",
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
"-
|
|
465
|
-
|
|
466
|
-
"",
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
"",
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
*
|
|
516
|
-
*
|
|
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
|
-
|
|
531
|
-
|
|
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
|
-
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
|
|
585
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
597
|
-
|
|
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
|
-
//
|
|
600
|
-
ctx.
|
|
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) +
|
|
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
|
-
|
|
700
|
-
|
|
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
|
-
|
|
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
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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:
|
|
912
|
+
agentDef: _fixGrillAgent,
|
|
744
913
|
title: "🐛 Bug 根因分析评审",
|
|
745
|
-
description: "
|
|
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:
|
|
931
|
+
agentDef: _docGrillAgent,
|
|
762
932
|
title: "📄 文档大纲评审",
|
|
763
|
-
description: "
|
|
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:
|
|
950
|
+
agentDef: _refactorGrillAgent,
|
|
780
951
|
title: "🔧 重构方案评审",
|
|
781
|
-
description: "
|
|
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:
|
|
969
|
+
agentDef: _testGrillAgent,
|
|
798
970
|
title: "🧪 测试计划评审",
|
|
799
|
-
description: "
|
|
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:
|
|
996
|
+
agentDef: _perfGrillAgent,
|
|
824
997
|
title: "⚡ 性能优化方案评审",
|
|
825
|
-
description: "
|
|
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
|
}
|