@nick848/sf-cli 1.0.20 → 1.0.22

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/dist/index.mjs CHANGED
@@ -104,7 +104,10 @@ async function executeWorkflow(ctx) {
104
104
  if (activeSession.phase === "context") {
105
105
  lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 1/9: \u9879\u76EE\u4E0A\u4E0B\u6587\u83B7\u53D6 \u2501\u2501\u2501"));
106
106
  lines.push("");
107
+ const loader = new LoadingIndicator2("\u8BFB\u53D6\u9879\u76EE\u914D\u7F6E");
108
+ loader.start();
107
109
  activeSession.context = await readProjectContext(ctx.options.workingDirectory);
110
+ loader.stop();
108
111
  lines.push(chalk9.gray(` \u9879\u76EE: ${activeSession.context.name}`));
109
112
  lines.push(chalk9.gray(` \u7C7B\u578B: ${activeSession.context.type}`));
110
113
  lines.push(chalk9.gray(` \u6846\u67B6: ${activeSession.context.framework || "\u672A\u8BC6\u522B"}`));
@@ -194,7 +197,7 @@ ${resource.analysis}`;
194
197
  lines.push("");
195
198
  lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 5/9: BDD \u573A\u666F\u62C6\u89E3 \u2501\u2501\u2501"));
196
199
  lines.push("");
197
- const loader = new LoadingIndicator("AI \u6B63\u5728\u751F\u6210 BDD \u573A\u666F");
200
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u751F\u6210 BDD \u573A\u666F");
198
201
  loader.start();
199
202
  try {
200
203
  activeSession.bddScenarios = await generateBDDScenariosWithAI(
@@ -229,7 +232,7 @@ ${resource.analysis}`;
229
232
  lines.push("");
230
233
  lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 6/9: OpenSpec \u89C4\u683C \u2501\u2501\u2501"));
231
234
  lines.push("");
232
- const loader = new LoadingIndicator("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
235
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
233
236
  loader.start();
234
237
  try {
235
238
  activeSession.specItems = await generateSpecItemsWithAI(
@@ -398,31 +401,85 @@ async function handleWorkflowInput(input, ctx) {
398
401
  activeSession.phase = "analysis";
399
402
  return executeWorkflow(ctx);
400
403
  }
401
- if (activeSession.phase === "spec") {
404
+ if (activeSession.phase === "spec" || activeSession.specFeedbackState === "waiting_confirm") {
402
405
  if (trimmed === "y" || trimmed === "yes" || trimmed === "\u786E\u8BA4") {
406
+ activeSession.specFeedbackState = void 0;
403
407
  activeSession.phase = "tdd";
404
408
  return executeWorkflow(ctx);
405
409
  }
406
- if (trimmed === "n" || trimmed === "no" || trimmed === "\u91CD\u65B0") {
407
- activeSession.bddScenarios = generateBDDScenarios(
408
- activeSession.refinedRequirement,
409
- activeSession.context,
410
- activeSession.clarificationQuestions,
411
- activeSession.referenceResources
412
- );
413
- activeSession.specItems = generateSpecItems(
414
- activeSession.refinedRequirement,
415
- activeSession.context,
416
- activeSession.bddScenarios,
417
- activeSession.clarificationQuestions,
418
- activeSession.referenceResources
419
- );
410
+ if (trimmed === "n" || trimmed === "no" || trimmed === "\u4E0D\u6EE1\u610F" || trimmed === "\u91CD\u65B0") {
411
+ activeSession.specFeedbackState = "collecting_feedback";
412
+ if (!activeSession.specFeedbacks) {
413
+ activeSession.specFeedbacks = [];
414
+ }
415
+ return {
416
+ output: chalk9.yellow("\n\u{1F4DD} \u8BF7\u8F93\u5165\u60A8\u5BF9\u89C4\u683C\u4E0D\u6EE1\u610F\u7684\u5730\u65B9:") + chalk9.gray("\n - \u9057\u6F0F\u7684\u529F\u80FD\u70B9") + chalk9.gray("\n - \u4E0D\u5408\u7406\u7684\u62C6\u5206") + chalk9.gray("\n - \u9700\u8981\u8865\u5145\u7684\u7EC6\u8282") + chalk9.gray("\n - \u5176\u4ED6\u6539\u8FDB\u5EFA\u8BAE") + chalk9.cyan('\n\n\u8F93\u5165\u5B8C\u6210\u540E\uFF0C\u8F93\u5165\u7A7A\u884C\u6216 "done" \u7ED3\u675F\u8F93\u5165')
417
+ };
418
+ }
419
+ }
420
+ if (activeSession.specFeedbackState === "collecting_feedback") {
421
+ if (trimmed === "" || trimmed === "done" || trimmed === "\u5B8C\u6210") {
422
+ if (!activeSession.specFeedbacks || activeSession.specFeedbacks.length === 0) {
423
+ return {
424
+ output: chalk9.yellow('\u26A0\uFE0F \u8BF7\u81F3\u5C11\u8F93\u5165\u4E00\u6761\u53CD\u9988\u610F\u89C1\uFF0C\u6216\u8F93\u5165 "cancel" \u53D6\u6D88')
425
+ };
426
+ }
427
+ activeSession.specFeedbackState = "feedback_received";
428
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u6839\u636E\u53CD\u9988\u91CD\u65B0\u751F\u6210\u89C4\u683C");
429
+ loader.start();
430
+ try {
431
+ activeSession.specItems = await generateSpecItemsWithFeedback(
432
+ activeSession.refinedRequirement,
433
+ activeSession.context,
434
+ activeSession.bddScenarios,
435
+ activeSession.clarificationQuestions,
436
+ activeSession.referenceResources,
437
+ activeSession.specFeedbacks,
438
+ ctx
439
+ );
440
+ loader.stop(chalk9.green(" \u2713 \u89C4\u683C\u5DF2\u6839\u636E\u53CD\u9988\u91CD\u65B0\u751F\u6210"));
441
+ } catch (error) {
442
+ loader.stop(chalk9.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840\u65B9\u6CD5\u91CD\u65B0\u751F\u6210"));
443
+ activeSession.specItems = generateSpecItems(
444
+ activeSession.refinedRequirement,
445
+ activeSession.context,
446
+ activeSession.bddScenarios,
447
+ activeSession.clarificationQuestions,
448
+ activeSession.referenceResources
449
+ );
450
+ }
420
451
  const specPath = await saveSpecFile(ctx.options.workingDirectory, activeSession);
452
+ activeSession.specFeedbackState = "waiting_confirm";
453
+ const lines = [];
454
+ lines.push(chalk9.cyan("\n\u2501\u2501\u2501 \u66F4\u65B0\u540E\u7684\u4EFB\u52A1\u6982\u89C8 \u2501\u2501\u2501"));
455
+ for (const item of activeSession.specItems.slice(0, 8)) {
456
+ const icon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
457
+ lines.push(chalk9.gray(` ${icon} [${item.id}] ${item.title}`));
458
+ }
459
+ if (activeSession.specItems.length > 8) {
460
+ lines.push(chalk9.gray(` ... \u5171 ${activeSession.specItems.length} \u4E2A\u4EFB\u52A1`));
461
+ }
421
462
  return {
422
- output: chalk9.cyan("\u{1F504} \u89C4\u683C\u5DF2\u91CD\u65B0\u751F\u6210") + chalk9.gray(`
423
- \u8DEF\u5F84: ${specPath}`) + chalk9.yellow("\n\n\u8BF7\u786E\u8BA4:") + chalk9.green("\n y - \u786E\u8BA4\u89C4\u683C") + chalk9.red("\n n - \u518D\u6B21\u91CD\u65B0\u751F\u6210")
463
+ output: lines.join("\n") + chalk9.green(`
464
+
465
+ \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u66F4\u65B0`) + chalk9.gray(`
466
+ \u8DEF\u5F84: ${specPath}`) + chalk9.yellow("\n\n\u8BF7\u786E\u8BA4:") + chalk9.green("\n y - \u786E\u8BA4\u89C4\u683C") + chalk9.red("\n n - \u4ECD\u6709\u95EE\u9898\uFF0C\u7EE7\u7EED\u53CD\u9988")
424
467
  };
425
468
  }
469
+ if (trimmed === "cancel" || trimmed === "\u53D6\u6D88") {
470
+ activeSession.specFeedbackState = "waiting_confirm";
471
+ activeSession.specFeedbacks = [];
472
+ return {
473
+ output: chalk9.gray("\u5DF2\u53D6\u6D88\u53CD\u9988\uFF0C\u8FD4\u56DE\u89C4\u683C\u786E\u8BA4") + chalk9.yellow("\n\n\u8BF7\u786E\u8BA4:") + chalk9.green("\n y - \u786E\u8BA4\u89C4\u683C") + chalk9.red("\n n - \u4E0D\u6EE1\u610F\uFF0C\u91CD\u65B0\u751F\u6210")
474
+ };
475
+ }
476
+ if (!activeSession.specFeedbacks) {
477
+ activeSession.specFeedbacks = [];
478
+ }
479
+ activeSession.specFeedbacks.push(trimmed);
480
+ return {
481
+ output: chalk9.gray(`\u5DF2\u8BB0\u5F55\u53CD\u9988 #${activeSession.specFeedbacks.length}: `) + chalk9.white(trimmed.slice(0, 50)) + chalk9.gray('\n\u7EE7\u7EED\u8F93\u5165\uFF0C\u6216\u8F93\u5165 "done" \u5B8C\u6210\u53CD\u9988')
482
+ };
426
483
  }
427
484
  if (activeSession.phase === "develop") {
428
485
  if (trimmed === "continue" || trimmed === "\u7EE7\u7EED" || trimmed === "done" || trimmed === "\u5B8C\u6210") {
@@ -574,107 +631,115 @@ function getCategoryLabel(category) {
574
631
  async function executeDevelopment(ctx, session) {
575
632
  const workingDir = ctx.options.workingDirectory;
576
633
  const files = [];
577
- const loader = new LoadingIndicator("AI \u6B63\u5728\u751F\u6210\u4EE3\u7801");
578
- loader.start();
579
- try {
580
- const systemPrompt = buildDevelopmentPrompt(session);
581
- const messages = [
582
- {
583
- role: "system",
584
- content: `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u524D\u7AEF\u5F00\u53D1\u5DE5\u7A0B\u5E08\u3002\u8BF7\u6839\u636E\u9700\u6C42\u89C4\u683C\u751F\u6210\u4EE3\u7801\u5B9E\u73B0\u3002
634
+ console.log("");
635
+ for (let i = 0; i < session.specItems.length; i++) {
636
+ const item = session.specItems[i];
637
+ const prefix = `[${i + 1}/${session.specItems.length}]`;
638
+ if (item.title.includes("\u6D4B\u8BD5") || item.title.includes("test")) {
639
+ continue;
640
+ }
641
+ const loader = new LoadingIndicator2(`${prefix} \u751F\u6210: ${item.title.slice(0, 20)}...`);
642
+ loader.start();
643
+ try {
644
+ const taskPrompt = buildTaskPrompt(session, item, i);
645
+ const messages = [
646
+ {
647
+ role: "system",
648
+ content: `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u524D\u7AEF\u5F00\u53D1\u5DE5\u7A0B\u5E08\u3002\u8BF7\u6839\u636E\u4EFB\u52A1\u63CF\u8FF0\u751F\u6210\u4EE3\u7801\u5B9E\u73B0\u3002
585
649
 
586
650
  \u26A0\uFE0F \u91CD\u8981\u89C4\u5219\uFF1A
587
- 1. \u6280\u672F\u5B9E\u73B0\u5FC5\u987B\u4E25\u683C\u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u5F00\u53D1\u89C4\u8303
588
- 2. \u53C2\u8003\u8D44\u6E90\uFF08\u5982\u679C\u6709\uFF09\u4EC5\u7528\u4E8E\u7406\u89E3\u4E1A\u52A1\u529F\u80FD\uFF0C\u4E0D\u8981\u590D\u5236\u5176\u6280\u672F\u5B9E\u73B0
589
- 3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808\u548C\u6846\u67B6
590
-
591
- \u4EE3\u7801\u8981\u6C42\uFF1A
592
- 1. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
593
- 2. \u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u4EE3\u7801\u98CE\u683C\u548C\u89C4\u8303
594
- 3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808
595
- 4. \u4EE3\u7801\u8981\u6709\u9002\u5F53\u7684\u6CE8\u91CA
596
- 5. \u8FD4\u56DE\u683C\u5F0F\uFF1A\u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u4EE3\u7801 \`\`\` \u5305\u88F9
651
+ 1. \u53EA\u751F\u6210\u5F53\u524D\u4EFB\u52A1\u76F8\u5173\u7684\u4EE3\u7801\uFF0C\u4E0D\u8981\u751F\u6210\u5176\u4ED6\u4EFB\u52A1\u7684\u4EE3\u7801
652
+ 2. \u6280\u672F\u5B9E\u73B0\u5FC5\u987B\u4E25\u683C\u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u5F00\u53D1\u89C4\u8303
653
+ 3. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
654
+ 4. \u8FD4\u56DE\u683C\u5F0F\uFF1A\u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u4EE3\u7801 \`\`\` \u5305\u88F9
597
655
 
598
656
  \u9879\u76EE\u4FE1\u606F\uFF1A
599
657
  - \u540D\u79F0: ${session.context?.name}
600
658
  - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
601
659
  - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
602
660
 
603
- ${session.context?.devStandards ? `\u3010\u5FC5\u987B\u9075\u5FAA\u7684\u5F00\u53D1\u89C4\u8303\u3011
604
- ${session.context.devStandards.slice(0, 2500)}` : ""}`
605
- },
606
- {
607
- role: "user",
608
- content: systemPrompt
661
+ ${session.context?.devStandards ? `\u3010\u5F00\u53D1\u89C4\u8303\u3011
662
+ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
663
+ },
664
+ {
665
+ role: "user",
666
+ content: taskPrompt
667
+ }
668
+ ];
669
+ const response = await ctx.modelService.sendMessage(messages, {
670
+ temperature: 0.3,
671
+ maxTokens: 4e3,
672
+ // 单个任务减少 token
673
+ agent: "frontend-dev",
674
+ timeout: 3e5
675
+ // 5分钟超时,确保复杂代码有足够时间
676
+ });
677
+ const codeBlocks = parseCodeBlocks(response.content);
678
+ if (codeBlocks.length > 0) {
679
+ for (const block of codeBlocks) {
680
+ const filePath = path5.join(workingDir, block.filename);
681
+ const dir = path5.dirname(filePath);
682
+ await fs4.mkdir(dir, { recursive: true });
683
+ await fs4.writeFile(filePath, block.code, "utf-8");
684
+ files.push(block.filename);
685
+ }
686
+ loader.stop(chalk9.green(`${prefix} \u2713 ${item.title.slice(0, 25)} (${codeBlocks.length} \u4E2A\u6587\u4EF6)`));
687
+ } else {
688
+ const implDir = path5.join(workingDir, "src");
689
+ await fs4.mkdir(implDir, { recursive: true });
690
+ const fileName = `${item.title.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_")}.ts`;
691
+ const filePath = path5.join(implDir, fileName);
692
+ await fs4.writeFile(filePath, `// TODO: ${item.title}
693
+ // ${item.description}
694
+ `, "utf-8");
695
+ files.push(`src/${fileName}`);
696
+ loader.stop(chalk9.yellow(`${prefix} \u26A0 \u751F\u6210\u57FA\u7840\u6A21\u677F: ${item.title.slice(0, 20)}`));
697
+ }
698
+ if (i < session.specItems.length - 1) {
699
+ await new Promise((resolve4) => setTimeout(resolve4, 500));
609
700
  }
610
- ];
611
- loader.update("\u6B63\u5728\u8C03\u7528 AI \u6A21\u578B");
612
- const response = await ctx.modelService.sendMessage(messages, {
613
- temperature: 0.3,
614
- maxTokens: 8e3,
615
- agent: "frontend-dev",
616
- timeout: 18e4
617
- // 3 分钟超时
618
- });
619
- loader.update("\u6B63\u5728\u89E3\u6790\u4EE3\u7801");
620
- const codeBlocks = parseCodeBlocks(response.content);
621
- for (const block of codeBlocks) {
622
- const filePath = path5.join(workingDir, block.filename);
623
- const dir = path5.dirname(filePath);
624
- await fs4.mkdir(dir, { recursive: true });
625
- await fs4.writeFile(filePath, block.code, "utf-8");
626
- files.push(block.filename);
627
- }
628
- if (files.length === 0) {
629
- const implDir = path5.join(workingDir, "src", "features");
630
- await fs4.mkdir(implDir, { recursive: true });
631
- const featureName = session.specItems[0]?.title || "feature";
632
- const fileName = `${featureName.replace(/[^a-zA-Z0-9]/g, "_")}.ts`;
633
- const filePath = path5.join(implDir, fileName);
634
- const stubCode = `/**
635
- * ${session.requirement}
636
- *
637
- * TODO: \u6B64\u6587\u4EF6\u7531 AI \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u6839\u636E\u9700\u6C42\u5B8C\u5584\u5B9E\u73B0
638
- */
639
-
640
- export function ${featureName.replace(/[^a-zA-Z0-9]/g, "")}() {
641
- // TODO: \u5B9E\u73B0\u529F\u80FD
642
- console.log('${featureName} - \u5F85\u5B9E\u73B0');
643
- }
644
- `;
645
- await fs4.writeFile(filePath, stubCode, "utf-8");
646
- files.push(`src/features/${fileName}`);
701
+ } catch (error) {
702
+ loader.stop(chalk9.red(`${prefix} \u2717 \u5931\u8D25: ${error.message.slice(0, 40)}`));
647
703
  }
648
- loader.stop(chalk9.green(` \u2713 \u5DF2\u751F\u6210 ${files.length} \u4E2A\u6587\u4EF6`));
704
+ }
705
+ if (files.length > 0) {
649
706
  return { success: true, files };
650
- } catch (error) {
651
- loader.stop();
707
+ } else {
652
708
  return {
653
709
  success: false,
654
710
  files: [],
655
- error: error.message
711
+ error: "\u6240\u6709\u4EFB\u52A1\u4EE3\u7801\u751F\u6210\u5931\u8D25"
656
712
  };
657
713
  }
658
714
  }
659
- function buildDevelopmentPrompt(session) {
715
+ function buildTaskPrompt(session, item, index) {
660
716
  const lines = [];
661
- lines.push("## \u9700\u6C42\u63CF\u8FF0");
662
- lines.push(session.refinedRequirement);
663
- lines.push("");
664
- lines.push("## BDD \u573A\u666F");
665
- for (const scenario of session.bddScenarios) {
666
- lines.push(`### ${scenario.feature}`);
667
- for (const s of scenario.scenarios) {
668
- lines.push(`- ${s.name}`);
717
+ lines.push(`## \u5F53\u524D\u4EFB\u52A1 (#${index + 1})`);
718
+ lines.push(`**\u6807\u9898**: ${item.title}`);
719
+ lines.push(`**\u63CF\u8FF0**: ${item.description}`);
720
+ lines.push(`**\u4F18\u5148\u7EA7**: ${item.priority}`);
721
+ if (item.tests && item.tests.length > 0) {
722
+ lines.push(`**\u9A8C\u6536\u6807\u51C6**:`);
723
+ for (const t of item.tests) {
724
+ lines.push(`- ${t}`);
669
725
  }
670
726
  }
671
727
  lines.push("");
672
- lines.push("## \u4EFB\u52A1\u5217\u8868");
673
- for (const item of session.specItems) {
674
- lines.push(`- [${item.id}] ${item.title}: ${item.description}`);
728
+ lines.push(`## \u6574\u4F53\u9700\u6C42\u80CC\u666F`);
729
+ lines.push(session.requirement);
730
+ const relatedScenario = session.bddScenarios.find(
731
+ (s) => item.title.includes(s.feature) || s.feature.includes(item.title)
732
+ );
733
+ if (relatedScenario) {
734
+ lines.push("");
735
+ lines.push(`## \u76F8\u5173 BDD \u573A\u666F`);
736
+ lines.push(`Feature: ${relatedScenario.feature}`);
737
+ for (const s of relatedScenario.scenarios.slice(0, 2)) {
738
+ lines.push(`- ${s.name}`);
739
+ }
675
740
  }
676
741
  lines.push("");
677
- lines.push("\u8BF7\u6839\u636E\u4EE5\u4E0A\u9700\u6C42\u89C4\u683C\u751F\u6210\u4EE3\u7801\u5B9E\u73B0\u3002");
742
+ lines.push("\u8BF7\u751F\u6210\u5B9E\u73B0\u6B64\u4EFB\u52A1\u7684\u4EE3\u7801\u3002\u53EA\u751F\u6210\u5F53\u524D\u4EFB\u52A1\u9700\u8981\u7684\u6587\u4EF6\u3002");
678
743
  return lines.join("\n");
679
744
  }
680
745
  function parseCodeBlocks(content) {
@@ -695,6 +760,8 @@ async function executeReview(ctx, session) {
695
760
  const workingDir = ctx.options.workingDirectory;
696
761
  const issues = [];
697
762
  const suggestions = [];
763
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u5BA1\u6838\u4EE3\u7801");
764
+ loader.start();
698
765
  try {
699
766
  const codeContents = [];
700
767
  for (const file of session.implFiles) {
@@ -757,7 +824,9 @@ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
757
824
  const response = await ctx.modelService.sendMessage(messages, {
758
825
  temperature: 0.2,
759
826
  maxTokens: 2e3,
760
- agent: "code-reviewer"
827
+ agent: "code-reviewer",
828
+ timeout: 18e4
829
+ // 3分钟超时
761
830
  });
762
831
  const result = response.content;
763
832
  const passed = result.includes("\u5BA1\u6838\u901A\u8FC7") || result.includes("\u901A\u8FC7") || !result.includes("\u4E0D\u901A\u8FC7");
@@ -779,8 +848,10 @@ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
779
848
  const lines = result.split("\n").filter((l) => l.includes("\u5EFA\u8BAE") || l.includes("\u6539\u8FDB") || l.includes("\u4F18\u5316"));
780
849
  suggestions.push(...lines.map((l) => l.replace(/^[-*]\s*/, "").trim()).filter((l) => l));
781
850
  }
851
+ loader.stop(passed ? chalk9.green(" \u2713 \u5BA1\u6838\u901A\u8FC7") : chalk9.yellow(" \u26A0 \u53D1\u73B0\u95EE\u9898"));
782
852
  return { passed, issues, suggestions };
783
853
  } catch (error) {
854
+ loader.stop(chalk9.red(" \u2717 \u5BA1\u6838\u51FA\u9519"));
784
855
  suggestions.push(`\u5BA1\u6838\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
785
856
  return { passed: true, issues, suggestions };
786
857
  }
@@ -986,7 +1057,8 @@ ${r.analysis}`).join("\n\n") : "\u65E0"}
986
1057
  ], {
987
1058
  temperature: 0.3,
988
1059
  maxTokens: 4e3,
989
- timeout: 12e4
1060
+ timeout: 3e5
1061
+ // 5分钟超时
990
1062
  });
991
1063
  try {
992
1064
  const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
@@ -1194,7 +1266,7 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
1194
1266
  - \u96C6\u6210\u6D4B\u8BD5
1195
1267
 
1196
1268
  \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\u3002`;
1197
- const loader = new LoadingIndicator("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
1269
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
1198
1270
  loader.start();
1199
1271
  try {
1200
1272
  const response = await ctx.modelService.sendMessage([
@@ -1202,25 +1274,131 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
1202
1274
  ], {
1203
1275
  temperature: 0.3,
1204
1276
  maxTokens: 4e3,
1205
- timeout: 12e4
1277
+ timeout: 3e5
1278
+ // 5分钟超时
1206
1279
  });
1207
1280
  const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
1208
1281
  if (jsonMatch) {
1209
- const parsed = JSON.parse(jsonMatch[1].trim());
1210
- loader.stop(chalk9.green(` \u2713 \u5DF2\u62C6\u5206 ${parsed.length} \u4E2A\u4EFB\u52A1`));
1211
- return parsed.map((item) => ({
1212
- id: item.id || `T${String(parsed.indexOf(item) + 1).padStart(3, "0")}`,
1213
- title: item.title,
1214
- description: item.description,
1215
- priority: item.priority || "medium",
1216
- files: [],
1217
- tests: item.acceptanceCriteria || []
1218
- }));
1282
+ try {
1283
+ const parsed = JSON.parse(jsonMatch[1].trim());
1284
+ loader.stop(chalk9.green(` \u2713 \u5DF2\u62C6\u5206 ${parsed.length} \u4E2A\u4EFB\u52A1`));
1285
+ return parsed.map((item, index) => ({
1286
+ id: item.id || `T${String(index + 1).padStart(3, "0")}`,
1287
+ title: item.title,
1288
+ description: item.description,
1289
+ priority: item.priority || "medium",
1290
+ files: [],
1291
+ tests: item.acceptanceCriteria || []
1292
+ }));
1293
+ } catch (parseError) {
1294
+ loader.stop(chalk9.yellow(` \u26A0 JSON \u89E3\u6790\u5931\u8D25: ${parseError.message.slice(0, 50)}`));
1295
+ return generateSpecItems(requirement, context, bddScenarios, questions, references);
1296
+ }
1219
1297
  }
1220
- loader.stop(chalk9.yellow(" \u26A0 \u89E3\u6790\u5931\u8D25\uFF0C\u4F7F\u7528\u57FA\u7840\u62C6\u5206"));
1298
+ try {
1299
+ const parsed = JSON.parse(response.content);
1300
+ if (Array.isArray(parsed)) {
1301
+ loader.stop(chalk9.green(` \u2713 \u5DF2\u62C6\u5206 ${parsed.length} \u4E2A\u4EFB\u52A1`));
1302
+ return parsed.map((item, index) => ({
1303
+ id: item.id || `T${String(index + 1).padStart(3, "0")}`,
1304
+ title: item.title,
1305
+ description: item.description,
1306
+ priority: item.priority || "medium",
1307
+ files: [],
1308
+ tests: item.acceptanceCriteria || []
1309
+ }));
1310
+ }
1311
+ } catch {
1312
+ }
1313
+ loader.stop(chalk9.yellow(" \u26A0 \u672A\u80FD\u89E3\u6790 AI \u54CD\u5E94\uFF0C\u4F7F\u7528\u57FA\u7840\u62C6\u5206"));
1221
1314
  return generateSpecItems(requirement, context, bddScenarios, questions, references);
1222
1315
  } catch (error) {
1223
- loader.stop(chalk9.yellow(" \u26A0 AI \u62C6\u5206\u5931\u8D25\uFF0C\u4F7F\u7528\u57FA\u7840\u62C6\u5206"));
1316
+ const errMsg = error.message.slice(0, 80);
1317
+ loader.stop(chalk9.yellow(` \u26A0 AI \u8C03\u7528\u5931\u8D25: ${errMsg}`));
1318
+ return generateSpecItems(requirement, context, bddScenarios, questions, references);
1319
+ }
1320
+ }
1321
+ async function generateSpecItemsWithFeedback(requirement, context, bddScenarios, questions, references, feedbacks, ctx) {
1322
+ const prompt2 = `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u9879\u76EE\u7ECF\u7406\u548C\u6280\u672F\u67B6\u6784\u5E08\u3002\u8BF7\u6839\u636E\u7528\u6237\u53CD\u9988\uFF0C\u91CD\u65B0\u62C6\u5206\u9700\u6C42\u4EFB\u52A1\u3002
1323
+
1324
+ ## \u9700\u6C42\u63CF\u8FF0
1325
+ ${requirement}
1326
+
1327
+ ## \u9879\u76EE\u4E0A\u4E0B\u6587
1328
+ - \u6280\u672F\u6808: ${context.techStack?.join(", ") || "TypeScript"}
1329
+ - \u6846\u67B6: ${context.framework || "\u672A\u6307\u5B9A"}
1330
+ ${context.devStandards ? `
1331
+ ## \u5F00\u53D1\u89C4\u8303\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09
1332
+ ${context.devStandards.slice(0, 2e3)}
1333
+ ` : ""}
1334
+
1335
+ ## \u4E4B\u524D\u7684 BDD \u573A\u666F
1336
+ ${bddScenarios.map((s) => `- Feature: ${s.feature}`).join("\n")}
1337
+
1338
+ ## \u7528\u6237\u53CD\u9988\uFF08\u5FC5\u987B\u89E3\u51B3\u8FD9\u4E9B\u95EE\u9898\uFF09
1339
+ ${feedbacks.map((f, i) => `${i + 1}. ${f}`).join("\n")}
1340
+
1341
+ ## \u8981\u6C42
1342
+ 1. **\u5FC5\u987B\u89E3\u51B3\u7528\u6237\u53CD\u9988\u4E2D\u7684\u6240\u6709\u95EE\u9898**
1343
+ 2. \u5982\u679C\u7528\u6237\u6307\u51FA\u9057\u6F0F\u7684\u529F\u80FD\u70B9\uFF0C\u8BF7\u8865\u5145\u76F8\u5173\u4EFB\u52A1
1344
+ 3. \u5982\u679C\u7528\u6237\u6307\u51FA\u62C6\u5206\u4E0D\u5408\u7406\uFF0C\u8BF7\u91CD\u65B0\u8C03\u6574\u7C92\u5EA6
1345
+ 4. \u5982\u679C\u7528\u6237\u9700\u8981\u66F4\u591A\u7EC6\u8282\uFF0C\u8BF7\u62C6\u5206\u5F97\u66F4\u7EC6\u81F4
1346
+ 5. \u6BCF\u4E2A\u4EFB\u52A1\u5E94\u8BE5\u5355\u4E00\u804C\u8D23\u30012-4\u5C0F\u65F6\u53EF\u5B8C\u6210
1347
+
1348
+ ## \u8F93\u51FA\u683C\u5F0F (JSON)
1349
+ \`\`\`json
1350
+ [
1351
+ {
1352
+ "id": "T001",
1353
+ "title": "\u4EFB\u52A1\u6807\u9898\uFF08\u7B80\u77ED\u660E\u786E\uFF09",
1354
+ "description": "\u8BE6\u7EC6\u63CF\u8FF0",
1355
+ "priority": "high",
1356
+ "acceptanceCriteria": ["\u9A8C\u6536\u6807\u51C61", "\u9A8C\u6536\u6807\u51C62"]
1357
+ }
1358
+ ]
1359
+ \`\`\`
1360
+
1361
+ \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\uFF0C\u786E\u4FDD\u89E3\u51B3\u4E86\u7528\u6237\u7684\u6240\u6709\u53CD\u9988\u3002`;
1362
+ try {
1363
+ const response = await ctx.modelService.sendMessage([
1364
+ { role: "user", content: prompt2 }
1365
+ ], {
1366
+ temperature: 0.3,
1367
+ maxTokens: 4e3,
1368
+ timeout: 3e5
1369
+ // 5分钟超时
1370
+ });
1371
+ const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
1372
+ if (jsonMatch) {
1373
+ try {
1374
+ const parsed = JSON.parse(jsonMatch[1].trim());
1375
+ return parsed.map((item, index) => ({
1376
+ id: item.id || `T${String(index + 1).padStart(3, "0")}`,
1377
+ title: item.title,
1378
+ description: item.description,
1379
+ priority: item.priority || "medium",
1380
+ files: [],
1381
+ tests: item.acceptanceCriteria || []
1382
+ }));
1383
+ } catch {
1384
+ }
1385
+ }
1386
+ try {
1387
+ const parsed = JSON.parse(response.content);
1388
+ if (Array.isArray(parsed)) {
1389
+ return parsed.map((item, index) => ({
1390
+ id: item.id || `T${String(index + 1).padStart(3, "0")}`,
1391
+ title: item.title,
1392
+ description: item.description,
1393
+ priority: item.priority || "medium",
1394
+ files: [],
1395
+ tests: item.acceptanceCriteria || []
1396
+ }));
1397
+ }
1398
+ } catch {
1399
+ }
1400
+ return generateSpecItems(requirement, context, bddScenarios, questions, references);
1401
+ } catch {
1224
1402
  return generateSpecItems(requirement, context, bddScenarios, questions, references);
1225
1403
  }
1226
1404
  }
@@ -1290,6 +1468,16 @@ function formatSpecFile(session) {
1290
1468
  lines.push("");
1291
1469
  }
1292
1470
  }
1471
+ if (session.specFeedbacks && session.specFeedbacks.length > 0) {
1472
+ lines.push("## \u89C4\u683C\u8FED\u4EE3\u53CD\u9988");
1473
+ lines.push("");
1474
+ for (let i = 0; i < session.specFeedbacks.length; i++) {
1475
+ lines.push(`${i + 1}. ${session.specFeedbacks[i]}`);
1476
+ }
1477
+ lines.push("");
1478
+ lines.push("---");
1479
+ lines.push("");
1480
+ }
1293
1481
  lines.push("## \u4EFB\u52A1\u5217\u8868");
1294
1482
  lines.push("");
1295
1483
  for (const item of session.specItems) {
@@ -1310,7 +1498,7 @@ async function generateTests(workingDir, session, ctx) {
1310
1498
  for (const scenario of session.bddScenarios) {
1311
1499
  const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
1312
1500
  const testPath = path5.join(testDir, `${testName}.test.ts`);
1313
- const loader = new LoadingIndicator(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
1501
+ const loader = new LoadingIndicator2(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
1314
1502
  loader.start();
1315
1503
  try {
1316
1504
  const content = await generateTestFileWithAI(scenario, session, ctx);
@@ -1365,7 +1553,9 @@ ${scenario.scenarios.map((s) => `
1365
1553
  { role: "user", content: prompt2 }
1366
1554
  ], {
1367
1555
  temperature: 0.3,
1368
- maxTokens: 4e3
1556
+ maxTokens: 4e3,
1557
+ timeout: 18e4
1558
+ // 3分钟超时
1369
1559
  });
1370
1560
  const codeMatch = response.content.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
1371
1561
  if (codeMatch) {
@@ -1464,7 +1654,10 @@ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
1464
1654
  const type = detectResourceType(url);
1465
1655
  let content = "";
1466
1656
  let analysis = "";
1657
+ const loader = new LoadingIndicator2(`\u83B7\u53D6 ${url.slice(0, 40)}...`);
1658
+ loader.start();
1467
1659
  try {
1660
+ loader.update("\u6B63\u5728\u83B7\u53D6\u7F51\u9875\u5185\u5BB9");
1468
1661
  const response = await fetch(url, {
1469
1662
  headers: {
1470
1663
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
@@ -1475,11 +1668,14 @@ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
1475
1668
  }
1476
1669
  content = await response.text();
1477
1670
  if (ctx.modelService.getCurrentModel()) {
1671
+ loader.update("\u6B63\u5728\u5206\u6790\u5185\u5BB9");
1478
1672
  analysis = await analyzeReferenceContent(url, content, type, ctx, projectContext);
1479
1673
  } else {
1480
1674
  analysis = extractBasicInfo(content, type);
1481
1675
  }
1676
+ loader.stop();
1482
1677
  } catch (error) {
1678
+ loader.stop();
1483
1679
  throw new Error(`\u65E0\u6CD5\u83B7\u53D6\u53C2\u8003\u8D44\u6E90: ${error.message}`);
1484
1680
  }
1485
1681
  return { url, type, content: content.slice(0, 1e4), analysis };
@@ -1548,14 +1744,16 @@ ${content.slice(0, 8e3)}
1548
1744
  \u6CE8\u610F\uFF1A\u8BF7\u4EE5 Markdown \u683C\u5F0F\u8F93\u51FA\uFF0C\u91CD\u70B9\u7A81\u51FA**\u4E1A\u52A1\u903B\u8F91**\u548C**\u529F\u80FD\u7279\u6027**\u3002
1549
1745
  \u6280\u672F\u5B9E\u73B0\u65B9\u6848\u7531\u9879\u76EE\u89C4\u8303\u51B3\u5B9A\uFF0C\u6B64\u5904\u4E0D\u6D89\u53CA\u3002
1550
1746
  `;
1551
- const loader = new LoadingIndicator("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
1747
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
1552
1748
  loader.start();
1553
1749
  try {
1554
1750
  const response = await ctx.modelService.sendMessage([
1555
1751
  { role: "user", content: prompt2 }
1556
1752
  ], {
1557
1753
  temperature: 0.3,
1558
- maxTokens: 4e3
1754
+ maxTokens: 4e3,
1755
+ timeout: 18e4
1756
+ // 3分钟超时
1559
1757
  });
1560
1758
  loader.stop(chalk9.green(" \u2713 \u5206\u6790\u5B8C\u6210"));
1561
1759
  return response.content;
@@ -1602,11 +1800,11 @@ function getActiveSession() {
1602
1800
  function clearActiveSession() {
1603
1801
  activeSession = null;
1604
1802
  }
1605
- var LoadingIndicator, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
1803
+ var LoadingIndicator2, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
1606
1804
  var init_new = __esm({
1607
1805
  "src/commands/new.ts"() {
1608
1806
  init_esm_shims();
1609
- LoadingIndicator = class {
1807
+ LoadingIndicator2 = class {
1610
1808
  frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1611
1809
  frameIndex = 0;
1612
1810
  interval = null;
@@ -1971,9 +2169,10 @@ var BaseAdapter = class {
1971
2169
  }
1972
2170
  /**
1973
2171
  * 获取超时时间
2172
+ * 默认 5 分钟,对于复杂的代码生成任务足够
1974
2173
  */
1975
2174
  getTimeout() {
1976
- return this.config?.timeout || 6e4;
2175
+ return this.config?.timeout || 3e5;
1977
2176
  }
1978
2177
  /**
1979
2178
  * 获取重试次数
@@ -8163,6 +8362,36 @@ ${chalk9.yellow("\u793A\u4F8B:")}
8163
8362
 
8164
8363
  // src/commands/model.ts
8165
8364
  init_esm_shims();
8365
+ var LoadingIndicator = class {
8366
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
8367
+ frameIndex = 0;
8368
+ interval = null;
8369
+ message;
8370
+ constructor(message) {
8371
+ this.message = message;
8372
+ }
8373
+ start() {
8374
+ process.stdout.write("\x1B[?25l");
8375
+ this.interval = setInterval(() => {
8376
+ const frame = this.frames[this.frameIndex];
8377
+ process.stdout.write(`\r${chalk9.cyan(frame)} ${this.message}...`);
8378
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
8379
+ }, 80);
8380
+ }
8381
+ stop(finalMessage) {
8382
+ if (this.interval) {
8383
+ clearInterval(this.interval);
8384
+ this.interval = null;
8385
+ }
8386
+ process.stdout.write("\x1B[?25h");
8387
+ if (finalMessage) {
8388
+ process.stdout.write(`\r${finalMessage}
8389
+ `);
8390
+ } else {
8391
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
8392
+ }
8393
+ }
8394
+ };
8166
8395
  async function handleModel(args, ctx) {
8167
8396
  const subCommand = args[0];
8168
8397
  const configManager = ctx.configManager;
@@ -8262,9 +8491,12 @@ async function verifyCurrentModel(configManager, modelService) {
8262
8491
  output: chalk9.red(`\u2717 \u672A\u77E5\u6A21\u578B: ${currentModel}`)
8263
8492
  };
8264
8493
  }
8494
+ const loader = new LoadingIndicator(`\u9A8C\u8BC1 ${modelInfo.name} API Key`);
8495
+ loader.start();
8265
8496
  try {
8266
8497
  const adapter = createAdapter(modelInfo.provider);
8267
8498
  const isValid = await adapter.validateApiKey(apiKey);
8499
+ loader.stop();
8268
8500
  if (isValid) {
8269
8501
  return {
8270
8502
  output: chalk9.green(`\u2713 API Key \u9A8C\u8BC1\u6210\u529F
@@ -8276,6 +8508,7 @@ async function verifyCurrentModel(configManager, modelService) {
8276
8508
  };
8277
8509
  }
8278
8510
  } catch (error) {
8511
+ loader.stop();
8279
8512
  return {
8280
8513
  output: chalk9.red(`\u2717 \u9A8C\u8BC1\u5931\u8D25: ${error.message}`)
8281
8514
  };
@@ -9487,6 +9720,36 @@ async function executeShell(command, ctx) {
9487
9720
  // src/commands/natural.ts
9488
9721
  init_esm_shims();
9489
9722
  init_new();
9723
+ var LoadingIndicator3 = class {
9724
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
9725
+ frameIndex = 0;
9726
+ interval = null;
9727
+ message;
9728
+ constructor(message) {
9729
+ this.message = message;
9730
+ }
9731
+ start() {
9732
+ process.stdout.write("\x1B[?25l");
9733
+ this.interval = setInterval(() => {
9734
+ const frame = this.frames[this.frameIndex];
9735
+ process.stdout.write(`\r${chalk9.cyan(frame)} ${this.message}...`);
9736
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
9737
+ }, 80);
9738
+ }
9739
+ stop(finalMessage) {
9740
+ if (this.interval) {
9741
+ clearInterval(this.interval);
9742
+ this.interval = null;
9743
+ }
9744
+ process.stdout.write("\x1B[?25h");
9745
+ if (finalMessage) {
9746
+ process.stdout.write(`\r${finalMessage}
9747
+ `);
9748
+ } else {
9749
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
9750
+ }
9751
+ }
9752
+ };
9490
9753
  async function handleNaturalLanguage(input, ctx) {
9491
9754
  const trimmedInput = input.trim();
9492
9755
  const session = getActiveSession();
@@ -9506,6 +9769,8 @@ async function handleNaturalLanguage(input, ctx) {
9506
9769
  contextUsed: 0
9507
9770
  };
9508
9771
  }
9772
+ const loader = new LoadingIndicator3("AI \u601D\u8003\u4E2D");
9773
+ loader.start();
9509
9774
  try {
9510
9775
  const response = await ctx.modelService.sendMessage(
9511
9776
  [
@@ -9523,6 +9788,7 @@ async function handleNaturalLanguage(input, ctx) {
9523
9788
  maxTokens: 2e3
9524
9789
  }
9525
9790
  );
9791
+ loader.stop();
9526
9792
  ctx.contextManager.addMessage({
9527
9793
  role: "user",
9528
9794
  content: trimmedInput
@@ -9536,6 +9802,7 @@ async function handleNaturalLanguage(input, ctx) {
9536
9802
  contextUsed: response.usage?.totalTokens || 0
9537
9803
  };
9538
9804
  } catch (error) {
9805
+ loader.stop();
9539
9806
  const errorMessage = error.message;
9540
9807
  if (errorMessage.includes("\u672A\u914D\u7F6E") || errorMessage.includes("\u672A\u521D\u59CB\u5316")) {
9541
9808
  return {