@nick848/sf-cli 1.0.21 → 1.0.23

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
@@ -2,7 +2,7 @@ import * as path5 from 'path';
2
2
  import path5__default from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import chalk9 from 'chalk';
5
- import * as fs4 from 'fs/promises';
5
+ import * as fs9 from 'fs/promises';
6
6
  import * as fs10 from 'fs';
7
7
  import * as crypto from 'crypto';
8
8
  import * as os from 'os';
@@ -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") {
@@ -581,28 +638,17 @@ async function executeDevelopment(ctx, session) {
581
638
  if (item.title.includes("\u6D4B\u8BD5") || item.title.includes("test")) {
582
639
  continue;
583
640
  }
584
- const loader = new LoadingIndicator(`${prefix} \u751F\u6210: ${item.title.slice(0, 20)}...`);
641
+ const loader = new LoadingIndicator2(`${prefix} \u751F\u6210: ${item.title.slice(0, 20)}...`);
585
642
  loader.start();
586
643
  try {
587
644
  const taskPrompt = buildTaskPrompt(session, item, i);
645
+ const structureInfo = session.context?.projectStructure;
646
+ const structureGuide = buildStructureGuide(structureInfo);
647
+ const systemPrompt = buildCodeGenerationSystemPrompt(session, structureGuide);
588
648
  const messages = [
589
649
  {
590
650
  role: "system",
591
- 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
592
-
593
- \u26A0\uFE0F \u91CD\u8981\u89C4\u5219\uFF1A
594
- 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
595
- 2. \u6280\u672F\u5B9E\u73B0\u5FC5\u987B\u4E25\u683C\u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u5F00\u53D1\u89C4\u8303
596
- 3. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
597
- 4. \u8FD4\u56DE\u683C\u5F0F\uFF1A\u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u4EE3\u7801 \`\`\` \u5305\u88F9
598
-
599
- \u9879\u76EE\u4FE1\u606F\uFF1A
600
- - \u540D\u79F0: ${session.context?.name}
601
- - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
602
- - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
603
-
604
- ${session.context?.devStandards ? `\u3010\u5F00\u53D1\u89C4\u8303\u3011
605
- ${session.context.devStandards.slice(0, 2e3)}` : ""}`
651
+ content: systemPrompt
606
652
  },
607
653
  {
608
654
  role: "user",
@@ -614,25 +660,25 @@ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
614
660
  maxTokens: 4e3,
615
661
  // 单个任务减少 token
616
662
  agent: "frontend-dev",
617
- timeout: 12e4
618
- // 2分钟超时
663
+ timeout: 3e5
664
+ // 5分钟超时,确保复杂代码有足够时间
619
665
  });
620
666
  const codeBlocks = parseCodeBlocks(response.content);
621
667
  if (codeBlocks.length > 0) {
622
668
  for (const block of codeBlocks) {
623
669
  const filePath = path5.join(workingDir, block.filename);
624
670
  const dir = path5.dirname(filePath);
625
- await fs4.mkdir(dir, { recursive: true });
626
- await fs4.writeFile(filePath, block.code, "utf-8");
671
+ await fs9.mkdir(dir, { recursive: true });
672
+ await fs9.writeFile(filePath, block.code, "utf-8");
627
673
  files.push(block.filename);
628
674
  }
629
675
  loader.stop(chalk9.green(`${prefix} \u2713 ${item.title.slice(0, 25)} (${codeBlocks.length} \u4E2A\u6587\u4EF6)`));
630
676
  } else {
631
677
  const implDir = path5.join(workingDir, "src");
632
- await fs4.mkdir(implDir, { recursive: true });
678
+ await fs9.mkdir(implDir, { recursive: true });
633
679
  const fileName = `${item.title.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_")}.ts`;
634
680
  const filePath = path5.join(implDir, fileName);
635
- await fs4.writeFile(filePath, `// TODO: ${item.title}
681
+ await fs9.writeFile(filePath, `// TODO: ${item.title}
636
682
  // ${item.description}
637
683
  `, "utf-8");
638
684
  files.push(`src/${fileName}`);
@@ -655,6 +701,91 @@ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
655
701
  };
656
702
  }
657
703
  }
704
+ function buildStructureGuide(structure) {
705
+ if (!structure) {
706
+ return `## \u9879\u76EE\u7ED3\u6784
707
+ - \u7EC4\u4EF6\u653E\u5728 src/components/
708
+ - \u9875\u9762\u653E\u5728 src/pages/
709
+ - \u5DE5\u5177\u51FD\u6570\u653E\u5728 src/utils/`;
710
+ }
711
+ const lines = ["## \u9879\u76EE\u7ED3\u6784\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09"];
712
+ if (structure.hasSrc) {
713
+ lines.push(`- \u6E90\u7801\u76EE\u5F55: ${structure.srcDir}/`);
714
+ }
715
+ if (structure.hasPages) {
716
+ lines.push(`- \u9875\u9762\u76EE\u5F55: ${structure.pagesDir}/ \uFF08\u9875\u9762\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
717
+ } else {
718
+ lines.push(`- \u9875\u9762\u76EE\u5F55: src/pages/ \uFF08\u9875\u9762\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
719
+ }
720
+ if (structure.hasComponents) {
721
+ lines.push(`- \u7EC4\u4EF6\u76EE\u5F55: ${structure.componentsDir}/ \uFF08\u901A\u7528\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
722
+ } else {
723
+ lines.push(`- \u7EC4\u4EF6\u76EE\u5F55: src/components/ \uFF08\u901A\u7528\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
724
+ }
725
+ if (structure.hasApi) {
726
+ lines.push(`- API \u76EE\u5F55: src/api/ \u6216 src/services/`);
727
+ }
728
+ if (structure.hasHooks) {
729
+ lines.push(`- Hooks \u76EE\u5F55: src/hooks/`);
730
+ }
731
+ if (structure.hasStore) {
732
+ lines.push(`- \u72B6\u6001\u7BA1\u7406\u76EE\u5F55: src/store/`);
733
+ }
734
+ if (structure.hasUtils) {
735
+ lines.push(`- \u5DE5\u5177\u51FD\u6570\u76EE\u5F55: src/utils/`);
736
+ }
737
+ lines.push("");
738
+ lines.push("\u26A0\uFE0F \u6587\u4EF6\u5FC5\u987B\u653E\u5728\u4E0A\u8FF0\u5BF9\u5E94\u76EE\u5F55\u4E2D\uFF0C\u4E0D\u8981\u968F\u610F\u521B\u5EFA\u65B0\u76EE\u5F55");
739
+ return lines.join("\n");
740
+ }
741
+ function buildCodeGenerationSystemPrompt(session, structureGuide) {
742
+ const framework = session.context?.framework || "React";
743
+ const techStack = session.context?.techStack?.join(", ") || "TypeScript";
744
+ const devStandards = session.context?.devStandards || "";
745
+ const standardsText = devStandards.slice(0, 6e3);
746
+ return `\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
747
+
748
+ ## \u26A0\uFE0F \u6838\u5FC3\u89C4\u5219\uFF08\u5FC5\u987B\u9075\u5B88\uFF09
749
+
750
+ 1. **\u8BED\u8A00**: \u5FC5\u987B\u4F7F\u7528 TypeScript (.tsx/.ts \u6587\u4EF6)
751
+ 2. **\u6846\u67B6**: ${framework}
752
+ 3. **\u53EA\u751F\u6210\u5F53\u524D\u4EFB\u52A1\u76F8\u5173\u7684\u4EE3\u7801**\uFF0C\u4E0D\u8981\u751F\u6210\u5176\u4ED6\u4EFB\u52A1\u7684\u4EE3\u7801
753
+ 4. **\u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u6B63\u786E**\uFF1A\u4E25\u683C\u6309\u7167\u9879\u76EE\u7ED3\u6784\u653E\u7F6E\u6587\u4EF6
754
+ 5. **\u5FC5\u987B\u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801**\uFF0C\u4E0D\u8981\u5199 TODO \u6216\u5360\u4F4D\u7B26
755
+ 6. **\u5982\u679C\u4EFB\u52A1\u6D89\u53CA\u9875\u9762**\uFF0C\u5FC5\u987B\u751F\u6210\u9875\u9762\u7EC4\u4EF6
756
+ 7. **\u5982\u679C\u4EFB\u52A1\u6D89\u53CA UI**\uFF0C\u5FC5\u987B\u751F\u6210\u5BF9\u5E94\u7684\u7EC4\u4EF6\u548C\u6837\u5F0F
757
+
758
+ ${structureGuide}
759
+
760
+ ## \u9879\u76EE\u4FE1\u606F
761
+
762
+ - \u540D\u79F0: ${session.context?.name || "\u672A\u547D\u540D\u9879\u76EE"}
763
+ - \u6846\u67B6: ${framework}
764
+ - \u6280\u672F\u6808: ${techStack}
765
+
766
+ ## \u5F00\u53D1\u89C4\u8303\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09
767
+
768
+ ${standardsText || `### \u9ED8\u8BA4\u89C4\u8303
769
+ - \u4F7F\u7528\u51FD\u6570\u5F0F\u7EC4\u4EF6
770
+ - \u4F7F\u7528 TypeScript \u7C7B\u578B\u5B9A\u4E49
771
+ - \u4F7F\u7528 async/await \u5904\u7406\u5F02\u6B65
772
+ - \u53D8\u91CF\u4F7F\u7528 camelCase\uFF0C\u7EC4\u4EF6\u4F7F\u7528 PascalCase
773
+ - \u5BFC\u51FA\u4F7F\u7528 export default \u6216 export const`}
774
+
775
+ ## \u8F93\u51FA\u683C\u5F0F
776
+
777
+ \u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u5305\u88F9\uFF0C\u4F8B\u5982\uFF1A
778
+
779
+ \`\`\`src/pages/HomePage.tsx
780
+ // \u4EE3\u7801\u5185\u5BB9
781
+ \`\`\`
782
+
783
+ \`\`\`src/components/Button/Button.tsx
784
+ // \u4EE3\u7801\u5185\u5BB9
785
+ \`\`\`
786
+
787
+ \u73B0\u5728\u8BF7\u6839\u636E\u4EFB\u52A1\u8981\u6C42\u751F\u6210\u4EE3\u7801\u3002`;
788
+ }
658
789
  function buildTaskPrompt(session, item, index) {
659
790
  const lines = [];
660
791
  lines.push(`## \u5F53\u524D\u4EFB\u52A1 (#${index + 1})`);
@@ -703,11 +834,13 @@ async function executeReview(ctx, session) {
703
834
  const workingDir = ctx.options.workingDirectory;
704
835
  const issues = [];
705
836
  const suggestions = [];
837
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u5BA1\u6838\u4EE3\u7801");
838
+ loader.start();
706
839
  try {
707
840
  const codeContents = [];
708
841
  for (const file of session.implFiles) {
709
842
  try {
710
- const content = await fs4.readFile(path5.join(workingDir, file), "utf-8");
843
+ const content = await fs9.readFile(path5.join(workingDir, file), "utf-8");
711
844
  codeContents.push(`// ${file}
712
845
  ${content}`);
713
846
  } catch {
@@ -716,7 +849,7 @@ ${content}`);
716
849
  const testContents = [];
717
850
  for (const file of session.testFiles) {
718
851
  try {
719
- const content = await fs4.readFile(path5.join(workingDir, file), "utf-8");
852
+ const content = await fs9.readFile(path5.join(workingDir, file), "utf-8");
720
853
  testContents.push(`// ${file}
721
854
  ${content}`);
722
855
  } catch {
@@ -765,7 +898,9 @@ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
765
898
  const response = await ctx.modelService.sendMessage(messages, {
766
899
  temperature: 0.2,
767
900
  maxTokens: 2e3,
768
- agent: "code-reviewer"
901
+ agent: "code-reviewer",
902
+ timeout: 18e4
903
+ // 3分钟超时
769
904
  });
770
905
  const result = response.content;
771
906
  const passed = result.includes("\u5BA1\u6838\u901A\u8FC7") || result.includes("\u901A\u8FC7") || !result.includes("\u4E0D\u901A\u8FC7");
@@ -787,8 +922,10 @@ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
787
922
  const lines = result.split("\n").filter((l) => l.includes("\u5EFA\u8BAE") || l.includes("\u6539\u8FDB") || l.includes("\u4F18\u5316"));
788
923
  suggestions.push(...lines.map((l) => l.replace(/^[-*]\s*/, "").trim()).filter((l) => l));
789
924
  }
925
+ loader.stop(passed ? chalk9.green(" \u2713 \u5BA1\u6838\u901A\u8FC7") : chalk9.yellow(" \u26A0 \u53D1\u73B0\u95EE\u9898"));
790
926
  return { passed, issues, suggestions };
791
927
  } catch (error) {
928
+ loader.stop(chalk9.red(" \u2717 \u5BA1\u6838\u51FA\u9519"));
792
929
  suggestions.push(`\u5BA1\u6838\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
793
930
  return { passed: true, issues, suggestions };
794
931
  }
@@ -806,9 +943,9 @@ async function readProjectContext(workingDir) {
806
943
  };
807
944
  const agentsPath = path5.join(workingDir, "AGENTS.md");
808
945
  try {
809
- const stats = await fs4.stat(agentsPath);
946
+ const stats = await fs9.stat(agentsPath);
810
947
  if (stats.size <= MAX_FILE_SIZE2) {
811
- context.agentsMd = await fs4.readFile(agentsPath, "utf-8");
948
+ context.agentsMd = await fs9.readFile(agentsPath, "utf-8");
812
949
  const nameMatch = context.agentsMd.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
813
950
  if (nameMatch) context.name = nameMatch[1];
814
951
  const typeMatch = context.agentsMd.match(/\|\s*项目类型\s*\|\s*([^\s|]+)/);
@@ -822,9 +959,9 @@ async function readProjectContext(workingDir) {
822
959
  }
823
960
  const configPath = path5.join(workingDir, "openspec", "config.yaml");
824
961
  try {
825
- const stats = await fs4.stat(configPath);
962
+ const stats = await fs9.stat(configPath);
826
963
  if (stats.size <= MAX_FILE_SIZE2) {
827
- context.configYaml = await fs4.readFile(configPath, "utf-8");
964
+ context.configYaml = await fs9.readFile(configPath, "utf-8");
828
965
  const nameMatch = context.configYaml.match(/name:\s*(.+)/);
829
966
  if (nameMatch) context.name = nameMatch[1].trim();
830
967
  const typeMatch = context.configYaml.match(/type:\s*(.+)/);
@@ -845,14 +982,173 @@ async function readProjectContext(workingDir) {
845
982
  }
846
983
  const devStandardsPath = path5.join(workingDir, ".sf-cli", "norms", "devstanded.md");
847
984
  try {
848
- const stats = await fs4.stat(devStandardsPath);
985
+ const stats = await fs9.stat(devStandardsPath);
849
986
  if (stats.size <= MAX_FILE_SIZE2) {
850
- context.devStandards = await fs4.readFile(devStandardsPath, "utf-8");
987
+ context.devStandards = await fs9.readFile(devStandardsPath, "utf-8");
851
988
  }
852
989
  } catch {
853
990
  }
991
+ if (!context.framework) {
992
+ const detectedFramework = await detectFramework2(workingDir);
993
+ if (detectedFramework) {
994
+ context.framework = detectedFramework;
995
+ context.techStack = [detectedFramework, ...context.techStack.filter((t) => t !== detectedFramework)];
996
+ }
997
+ }
998
+ if (!context.devStandards) {
999
+ context.devStandards = await analyzeProjectForStandards(workingDir, context);
1000
+ }
1001
+ context.projectStructure = await analyzeProjectStructure(workingDir);
854
1002
  return context;
855
1003
  }
1004
+ async function detectFramework2(workingDir) {
1005
+ try {
1006
+ const pkgPath = path5.join(workingDir, "package.json");
1007
+ const pkgContent = await fs9.readFile(pkgPath, "utf-8");
1008
+ const pkg = JSON.parse(pkgContent);
1009
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1010
+ if (deps["react"] || deps["next"]) return "React";
1011
+ if (deps["vue"] || deps["nuxt"]) return "Vue";
1012
+ if (deps["angular"] || deps["@angular/core"]) return "Angular";
1013
+ if (deps["svelte"]) return "Svelte";
1014
+ if (deps["solid-js"]) return "Solid";
1015
+ if (deps["vite"] || deps["webpack"]) return "JavaScript";
1016
+ if (deps["typescript"]) return "TypeScript";
1017
+ return null;
1018
+ } catch {
1019
+ return null;
1020
+ }
1021
+ }
1022
+ async function analyzeProjectForStandards(workingDir, context) {
1023
+ const standards = [];
1024
+ standards.push(`# \u9879\u76EE\u5F00\u53D1\u89C4\u8303\uFF08\u81EA\u52A8\u751F\u6210\uFF09
1025
+ > \u9879\u76EE\u540D\u79F0: ${context.name}
1026
+ > \u6846\u67B6: ${context.framework || "\u672A\u68C0\u6D4B\u5230"}
1027
+ > \u6280\u672F\u6808: ${context.techStack.join(", ") || "\u672A\u68C0\u6D4B\u5230"}
1028
+ `);
1029
+ try {
1030
+ const tsConfigPath = path5.join(workingDir, "tsconfig.json");
1031
+ await fs9.access(tsConfigPath);
1032
+ standards.push("\n## \u8BED\u8A00\n- \u4F7F\u7528 TypeScript");
1033
+ } catch {
1034
+ standards.push("\n## \u8BED\u8A00\n- \u4F7F\u7528 JavaScript");
1035
+ }
1036
+ try {
1037
+ const srcDir = path5.join(workingDir, "src");
1038
+ const files = await listFilesDeep(srcDir);
1039
+ const hasCss = files.some((f) => f.endsWith(".css"));
1040
+ const hasScss = files.some((f) => f.endsWith(".scss") || f.endsWith(".sass"));
1041
+ const hasLess = files.some((f) => f.endsWith(".less"));
1042
+ const hasStyled = files.some((f) => f.includes(".styled.") || f.includes("styled-components"));
1043
+ if (hasScss) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 SCSS/Sass");
1044
+ else if (hasLess) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 Less");
1045
+ else if (hasStyled) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 styled-components");
1046
+ else if (hasCss) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 CSS");
1047
+ } catch {
1048
+ }
1049
+ try {
1050
+ const srcDir = path5.join(workingDir, "src");
1051
+ const entries = await fs9.readdir(srcDir, { withFileTypes: true });
1052
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1053
+ if (dirs.includes("components")) standards.push("\n## \u76EE\u5F55\u7ED3\u6784\n- \u7EC4\u4EF6\u653E\u5728 src/components/");
1054
+ if (dirs.includes("pages") || dirs.includes("views")) standards.push("- \u9875\u9762\u653E\u5728 src/pages/ \u6216 src/views/");
1055
+ if (dirs.includes("hooks")) standards.push("- Hooks \u653E\u5728 src/hooks/");
1056
+ if (dirs.includes("utils")) standards.push("- \u5DE5\u5177\u51FD\u6570\u653E\u5728 src/utils/");
1057
+ if (dirs.includes("api") || dirs.includes("services")) standards.push("- API \u8C03\u7528\u653E\u5728 src/api/ \u6216 src/services/");
1058
+ if (dirs.includes("store") || dirs.includes("stores")) standards.push("- \u72B6\u6001\u7BA1\u7406\u653E\u5728 src/store/");
1059
+ } catch {
1060
+ }
1061
+ standards.push("\n## \u4EE3\u7801\u89C4\u8303");
1062
+ standards.push("- \u4F7F\u7528 ES6+ \u8BED\u6CD5");
1063
+ standards.push("- \u7EC4\u4EF6\u4F7F\u7528\u51FD\u6570\u5F0F\u5199\u6CD5");
1064
+ standards.push("- \u4F7F\u7528 async/await \u5904\u7406\u5F02\u6B65");
1065
+ standards.push("- \u53D8\u91CF\u4F7F\u7528 camelCase\uFF0C\u7EC4\u4EF6\u4F7F\u7528 PascalCase");
1066
+ standards.push("- \u5E38\u91CF\u4F7F\u7528 UPPER_SNAKE_CASE");
1067
+ return standards.join("\n");
1068
+ }
1069
+ async function listFilesDeep(dir, maxDepth = 3) {
1070
+ const files = [];
1071
+ async function scan(currentDir, depth) {
1072
+ if (depth > maxDepth) return;
1073
+ try {
1074
+ const entries = await fs9.readdir(currentDir, { withFileTypes: true });
1075
+ for (const entry of entries) {
1076
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
1077
+ const fullPath = path5.join(currentDir, entry.name);
1078
+ if (entry.isDirectory()) {
1079
+ await scan(fullPath, depth + 1);
1080
+ } else {
1081
+ files.push(fullPath);
1082
+ }
1083
+ }
1084
+ } catch {
1085
+ }
1086
+ }
1087
+ await scan(dir, 0);
1088
+ return files;
1089
+ }
1090
+ async function analyzeProjectStructure(workingDir) {
1091
+ const structure = {
1092
+ hasSrc: false,
1093
+ hasPages: false,
1094
+ hasComponents: false,
1095
+ hasApi: false,
1096
+ hasHooks: false,
1097
+ hasStore: false,
1098
+ hasUtils: false,
1099
+ srcDir: "src",
1100
+ pagesDir: "",
1101
+ componentsDir: ""
1102
+ };
1103
+ try {
1104
+ const srcDir = path5.join(workingDir, "src");
1105
+ try {
1106
+ const srcStat = await fs9.stat(srcDir);
1107
+ if (srcStat.isDirectory()) {
1108
+ structure.hasSrc = true;
1109
+ structure.srcDir = "src";
1110
+ const srcEntries = await fs9.readdir(srcDir, { withFileTypes: true });
1111
+ const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
1112
+ if (srcDirs.includes("pages")) {
1113
+ structure.hasPages = true;
1114
+ structure.pagesDir = "src/pages";
1115
+ } else if (srcDirs.includes("views")) {
1116
+ structure.hasPages = true;
1117
+ structure.pagesDir = "src/views";
1118
+ }
1119
+ if (srcDirs.includes("components")) {
1120
+ structure.hasComponents = true;
1121
+ structure.componentsDir = "src/components";
1122
+ }
1123
+ if (srcDirs.includes("api") || srcDirs.includes("services")) {
1124
+ structure.hasApi = true;
1125
+ }
1126
+ if (srcDirs.includes("hooks")) {
1127
+ structure.hasHooks = true;
1128
+ }
1129
+ if (srcDirs.includes("store") || srcDirs.includes("stores")) {
1130
+ structure.hasStore = true;
1131
+ }
1132
+ if (srcDirs.includes("utils")) {
1133
+ structure.hasUtils = true;
1134
+ }
1135
+ }
1136
+ } catch {
1137
+ const rootEntries = await fs9.readdir(workingDir, { withFileTypes: true });
1138
+ const rootDirs = rootEntries.filter((e) => e.isDirectory()).map((e) => e.name);
1139
+ if (rootDirs.includes("pages")) {
1140
+ structure.hasPages = true;
1141
+ structure.pagesDir = "pages";
1142
+ }
1143
+ if (rootDirs.includes("components")) {
1144
+ structure.hasComponents = true;
1145
+ structure.componentsDir = "components";
1146
+ }
1147
+ }
1148
+ } catch {
1149
+ }
1150
+ return structure;
1151
+ }
856
1152
  function analyzeComplexity(requirement, context) {
857
1153
  let score = 3;
858
1154
  if (requirement.length > 100) score += 1;
@@ -994,7 +1290,8 @@ ${r.analysis}`).join("\n\n") : "\u65E0"}
994
1290
  ], {
995
1291
  temperature: 0.3,
996
1292
  maxTokens: 4e3,
997
- timeout: 12e4
1293
+ timeout: 3e5
1294
+ // 5分钟超时
998
1295
  });
999
1296
  try {
1000
1297
  const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
@@ -1202,7 +1499,7 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
1202
1499
  - \u96C6\u6210\u6D4B\u8BD5
1203
1500
 
1204
1501
  \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\u3002`;
1205
- const loader = new LoadingIndicator("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
1502
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
1206
1503
  loader.start();
1207
1504
  try {
1208
1505
  const response = await ctx.modelService.sendMessage([
@@ -1210,8 +1507,8 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
1210
1507
  ], {
1211
1508
  temperature: 0.3,
1212
1509
  maxTokens: 4e3,
1213
- timeout: 18e4
1214
- // 增加到3分钟
1510
+ timeout: 3e5
1511
+ // 5分钟超时
1215
1512
  });
1216
1513
  const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
1217
1514
  if (jsonMatch) {
@@ -1254,12 +1551,96 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
1254
1551
  return generateSpecItems(requirement, context, bddScenarios, questions, references);
1255
1552
  }
1256
1553
  }
1554
+ async function generateSpecItemsWithFeedback(requirement, context, bddScenarios, questions, references, feedbacks, ctx) {
1555
+ 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
1556
+
1557
+ ## \u9700\u6C42\u63CF\u8FF0
1558
+ ${requirement}
1559
+
1560
+ ## \u9879\u76EE\u4E0A\u4E0B\u6587
1561
+ - \u6280\u672F\u6808: ${context.techStack?.join(", ") || "TypeScript"}
1562
+ - \u6846\u67B6: ${context.framework || "\u672A\u6307\u5B9A"}
1563
+ ${context.devStandards ? `
1564
+ ## \u5F00\u53D1\u89C4\u8303\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09
1565
+ ${context.devStandards.slice(0, 2e3)}
1566
+ ` : ""}
1567
+
1568
+ ## \u4E4B\u524D\u7684 BDD \u573A\u666F
1569
+ ${bddScenarios.map((s) => `- Feature: ${s.feature}`).join("\n")}
1570
+
1571
+ ## \u7528\u6237\u53CD\u9988\uFF08\u5FC5\u987B\u89E3\u51B3\u8FD9\u4E9B\u95EE\u9898\uFF09
1572
+ ${feedbacks.map((f, i) => `${i + 1}. ${f}`).join("\n")}
1573
+
1574
+ ## \u8981\u6C42
1575
+ 1. **\u5FC5\u987B\u89E3\u51B3\u7528\u6237\u53CD\u9988\u4E2D\u7684\u6240\u6709\u95EE\u9898**
1576
+ 2. \u5982\u679C\u7528\u6237\u6307\u51FA\u9057\u6F0F\u7684\u529F\u80FD\u70B9\uFF0C\u8BF7\u8865\u5145\u76F8\u5173\u4EFB\u52A1
1577
+ 3. \u5982\u679C\u7528\u6237\u6307\u51FA\u62C6\u5206\u4E0D\u5408\u7406\uFF0C\u8BF7\u91CD\u65B0\u8C03\u6574\u7C92\u5EA6
1578
+ 4. \u5982\u679C\u7528\u6237\u9700\u8981\u66F4\u591A\u7EC6\u8282\uFF0C\u8BF7\u62C6\u5206\u5F97\u66F4\u7EC6\u81F4
1579
+ 5. \u6BCF\u4E2A\u4EFB\u52A1\u5E94\u8BE5\u5355\u4E00\u804C\u8D23\u30012-4\u5C0F\u65F6\u53EF\u5B8C\u6210
1580
+
1581
+ ## \u8F93\u51FA\u683C\u5F0F (JSON)
1582
+ \`\`\`json
1583
+ [
1584
+ {
1585
+ "id": "T001",
1586
+ "title": "\u4EFB\u52A1\u6807\u9898\uFF08\u7B80\u77ED\u660E\u786E\uFF09",
1587
+ "description": "\u8BE6\u7EC6\u63CF\u8FF0",
1588
+ "priority": "high",
1589
+ "acceptanceCriteria": ["\u9A8C\u6536\u6807\u51C61", "\u9A8C\u6536\u6807\u51C62"]
1590
+ }
1591
+ ]
1592
+ \`\`\`
1593
+
1594
+ \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\uFF0C\u786E\u4FDD\u89E3\u51B3\u4E86\u7528\u6237\u7684\u6240\u6709\u53CD\u9988\u3002`;
1595
+ try {
1596
+ const response = await ctx.modelService.sendMessage([
1597
+ { role: "user", content: prompt2 }
1598
+ ], {
1599
+ temperature: 0.3,
1600
+ maxTokens: 4e3,
1601
+ timeout: 3e5
1602
+ // 5分钟超时
1603
+ });
1604
+ const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
1605
+ if (jsonMatch) {
1606
+ try {
1607
+ const parsed = JSON.parse(jsonMatch[1].trim());
1608
+ return parsed.map((item, index) => ({
1609
+ id: item.id || `T${String(index + 1).padStart(3, "0")}`,
1610
+ title: item.title,
1611
+ description: item.description,
1612
+ priority: item.priority || "medium",
1613
+ files: [],
1614
+ tests: item.acceptanceCriteria || []
1615
+ }));
1616
+ } catch {
1617
+ }
1618
+ }
1619
+ try {
1620
+ const parsed = JSON.parse(response.content);
1621
+ if (Array.isArray(parsed)) {
1622
+ return parsed.map((item, index) => ({
1623
+ id: item.id || `T${String(index + 1).padStart(3, "0")}`,
1624
+ title: item.title,
1625
+ description: item.description,
1626
+ priority: item.priority || "medium",
1627
+ files: [],
1628
+ tests: item.acceptanceCriteria || []
1629
+ }));
1630
+ }
1631
+ } catch {
1632
+ }
1633
+ return generateSpecItems(requirement, context, bddScenarios, questions, references);
1634
+ } catch {
1635
+ return generateSpecItems(requirement, context, bddScenarios, questions, references);
1636
+ }
1637
+ }
1257
1638
  async function saveSpecFile(workingDir, session) {
1258
1639
  const specDir = path5.join(workingDir, "openspec", "changes");
1259
- await fs4.mkdir(specDir, { recursive: true });
1640
+ await fs9.mkdir(specDir, { recursive: true });
1260
1641
  const specPath = path5.join(specDir, `${session.id}-spec.md`);
1261
1642
  const content = formatSpecFile(session);
1262
- await fs4.writeFile(specPath, content, "utf-8");
1643
+ await fs9.writeFile(specPath, content, "utf-8");
1263
1644
  return specPath;
1264
1645
  }
1265
1646
  function formatSpecFile(session) {
@@ -1320,6 +1701,16 @@ function formatSpecFile(session) {
1320
1701
  lines.push("");
1321
1702
  }
1322
1703
  }
1704
+ if (session.specFeedbacks && session.specFeedbacks.length > 0) {
1705
+ lines.push("## \u89C4\u683C\u8FED\u4EE3\u53CD\u9988");
1706
+ lines.push("");
1707
+ for (let i = 0; i < session.specFeedbacks.length; i++) {
1708
+ lines.push(`${i + 1}. ${session.specFeedbacks[i]}`);
1709
+ }
1710
+ lines.push("");
1711
+ lines.push("---");
1712
+ lines.push("");
1713
+ }
1323
1714
  lines.push("## \u4EFB\u52A1\u5217\u8868");
1324
1715
  lines.push("");
1325
1716
  for (const item of session.specItems) {
@@ -1334,22 +1725,22 @@ function formatSpecFile(session) {
1334
1725
  }
1335
1726
  async function generateTests(workingDir, session, ctx) {
1336
1727
  const testDir = path5.join(workingDir, "tests");
1337
- await fs4.mkdir(testDir, { recursive: true });
1728
+ await fs9.mkdir(testDir, { recursive: true });
1338
1729
  const testFiles = [];
1339
1730
  if (ctx?.modelService) {
1340
1731
  for (const scenario of session.bddScenarios) {
1341
1732
  const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
1342
1733
  const testPath = path5.join(testDir, `${testName}.test.ts`);
1343
- const loader = new LoadingIndicator(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
1734
+ const loader = new LoadingIndicator2(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
1344
1735
  loader.start();
1345
1736
  try {
1346
1737
  const content = await generateTestFileWithAI(scenario, session, ctx);
1347
- await fs4.writeFile(testPath, content, "utf-8");
1738
+ await fs9.writeFile(testPath, content, "utf-8");
1348
1739
  testFiles.push(`tests/${testName}.test.ts`);
1349
1740
  loader.stop(chalk9.green(` \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210`));
1350
1741
  } catch {
1351
1742
  const content = generateTestFile(scenario, session);
1352
- await fs4.writeFile(testPath, content, "utf-8");
1743
+ await fs9.writeFile(testPath, content, "utf-8");
1353
1744
  testFiles.push(`tests/${testName}.test.ts`);
1354
1745
  loader.stop(chalk9.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840\u6D4B\u8BD5\u6A21\u677F"));
1355
1746
  }
@@ -1359,7 +1750,7 @@ async function generateTests(workingDir, session, ctx) {
1359
1750
  const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
1360
1751
  const testPath = path5.join(testDir, `${testName}.test.ts`);
1361
1752
  const content = generateTestFile(scenario, session);
1362
- await fs4.writeFile(testPath, content, "utf-8");
1753
+ await fs9.writeFile(testPath, content, "utf-8");
1363
1754
  testFiles.push(`tests/${testName}.test.ts`);
1364
1755
  }
1365
1756
  }
@@ -1395,7 +1786,9 @@ ${scenario.scenarios.map((s) => `
1395
1786
  { role: "user", content: prompt2 }
1396
1787
  ], {
1397
1788
  temperature: 0.3,
1398
- maxTokens: 4e3
1789
+ maxTokens: 4e3,
1790
+ timeout: 18e4
1791
+ // 3分钟超时
1399
1792
  });
1400
1793
  const codeMatch = response.content.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
1401
1794
  if (codeMatch) {
@@ -1451,7 +1844,7 @@ function generateTestFile(scenario, session) {
1451
1844
  async function archiveWorkflow(workingDir) {
1452
1845
  if (!activeSession) return;
1453
1846
  const archiveDir = path5.join(workingDir, "openspec", "spec");
1454
- await fs4.mkdir(archiveDir, { recursive: true });
1847
+ await fs9.mkdir(archiveDir, { recursive: true });
1455
1848
  const archivePath = path5.join(archiveDir, `${activeSession.id}.md`);
1456
1849
  const content = `# \u5F52\u6863: ${activeSession.requirement.slice(0, 50)}
1457
1850
 
@@ -1478,7 +1871,7 @@ ${activeSession.refinedRequirement}
1478
1871
 
1479
1872
  ${activeSession.testFiles.map((f) => `- ${f}`).join("\n") || "\u65E0"}
1480
1873
  `;
1481
- await fs4.writeFile(archivePath, content, "utf-8");
1874
+ await fs9.writeFile(archivePath, content, "utf-8");
1482
1875
  }
1483
1876
  function generateSessionId() {
1484
1877
  const timestamp = Date.now().toString(36);
@@ -1494,7 +1887,10 @@ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
1494
1887
  const type = detectResourceType(url);
1495
1888
  let content = "";
1496
1889
  let analysis = "";
1890
+ const loader = new LoadingIndicator2(`\u83B7\u53D6 ${url.slice(0, 40)}...`);
1891
+ loader.start();
1497
1892
  try {
1893
+ loader.update("\u6B63\u5728\u83B7\u53D6\u7F51\u9875\u5185\u5BB9");
1498
1894
  const response = await fetch(url, {
1499
1895
  headers: {
1500
1896
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
@@ -1505,11 +1901,14 @@ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
1505
1901
  }
1506
1902
  content = await response.text();
1507
1903
  if (ctx.modelService.getCurrentModel()) {
1904
+ loader.update("\u6B63\u5728\u5206\u6790\u5185\u5BB9");
1508
1905
  analysis = await analyzeReferenceContent(url, content, type, ctx, projectContext);
1509
1906
  } else {
1510
1907
  analysis = extractBasicInfo(content, type);
1511
1908
  }
1909
+ loader.stop();
1512
1910
  } catch (error) {
1911
+ loader.stop();
1513
1912
  throw new Error(`\u65E0\u6CD5\u83B7\u53D6\u53C2\u8003\u8D44\u6E90: ${error.message}`);
1514
1913
  }
1515
1914
  return { url, type, content: content.slice(0, 1e4), analysis };
@@ -1578,14 +1977,16 @@ ${content.slice(0, 8e3)}
1578
1977
  \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
1579
1978
  \u6280\u672F\u5B9E\u73B0\u65B9\u6848\u7531\u9879\u76EE\u89C4\u8303\u51B3\u5B9A\uFF0C\u6B64\u5904\u4E0D\u6D89\u53CA\u3002
1580
1979
  `;
1581
- const loader = new LoadingIndicator("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
1980
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
1582
1981
  loader.start();
1583
1982
  try {
1584
1983
  const response = await ctx.modelService.sendMessage([
1585
1984
  { role: "user", content: prompt2 }
1586
1985
  ], {
1587
1986
  temperature: 0.3,
1588
- maxTokens: 4e3
1987
+ maxTokens: 4e3,
1988
+ timeout: 18e4
1989
+ // 3分钟超时
1589
1990
  });
1590
1991
  loader.stop(chalk9.green(" \u2713 \u5206\u6790\u5B8C\u6210"));
1591
1992
  return response.content;
@@ -1632,11 +2033,11 @@ function getActiveSession() {
1632
2033
  function clearActiveSession() {
1633
2034
  activeSession = null;
1634
2035
  }
1635
- var LoadingIndicator, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
2036
+ var LoadingIndicator2, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
1636
2037
  var init_new = __esm({
1637
2038
  "src/commands/new.ts"() {
1638
2039
  init_esm_shims();
1639
- LoadingIndicator = class {
2040
+ LoadingIndicator2 = class {
1640
2041
  frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1641
2042
  frameIndex = 0;
1642
2043
  interval = null;
@@ -1746,7 +2147,7 @@ var ConfigManager = class {
1746
2147
  this.projectPath = projectPath;
1747
2148
  this.configPath = path5.join(projectPath, ".sf-cli", "config.json");
1748
2149
  try {
1749
- const content = await fs4.readFile(this.configPath, "utf-8");
2150
+ const content = await fs9.readFile(this.configPath, "utf-8");
1750
2151
  const loaded = JSON.parse(content);
1751
2152
  if (loaded.apiKey && loaded.apiKeyEncrypted) {
1752
2153
  loaded.apiKey = this.decrypt(loaded.apiKey);
@@ -1765,8 +2166,8 @@ var ConfigManager = class {
1765
2166
  configToSave.apiKey = encrypted;
1766
2167
  configToSave.apiKeyEncrypted = true;
1767
2168
  }
1768
- await fs4.mkdir(path5.dirname(this.configPath), { recursive: true });
1769
- await fs4.writeFile(
2169
+ await fs9.mkdir(path5.dirname(this.configPath), { recursive: true });
2170
+ await fs9.writeFile(
1770
2171
  this.configPath,
1771
2172
  JSON.stringify(configToSave, null, 2),
1772
2173
  "utf-8"
@@ -2001,9 +2402,10 @@ var BaseAdapter = class {
2001
2402
  }
2002
2403
  /**
2003
2404
  * 获取超时时间
2405
+ * 默认 5 分钟,对于复杂的代码生成任务足够
2004
2406
  */
2005
2407
  getTimeout() {
2006
- return this.config?.timeout || 6e4;
2408
+ return this.config?.timeout || 3e5;
2007
2409
  }
2008
2410
  /**
2009
2411
  * 获取重试次数
@@ -2863,8 +3265,8 @@ var ModelService = class {
2863
3265
  const dailyPath = path5.join(this.statsPath, "daily.json");
2864
3266
  const totalPath = path5.join(this.statsPath, "total.json");
2865
3267
  const [daily, total] = await Promise.all([
2866
- fs4.readFile(dailyPath, "utf-8").catch(() => "{}"),
2867
- fs4.readFile(totalPath, "utf-8").catch(() => '{"totalInput":0,"totalOutput":0}')
3268
+ fs9.readFile(dailyPath, "utf-8").catch(() => "{}"),
3269
+ fs9.readFile(totalPath, "utf-8").catch(() => '{"totalInput":0,"totalOutput":0}')
2868
3270
  ]);
2869
3271
  const dailyData = JSON.parse(daily);
2870
3272
  const totalData = JSON.parse(total);
@@ -2877,12 +3279,12 @@ var ModelService = class {
2877
3279
  async saveStats() {
2878
3280
  if (!this.statsPath) return;
2879
3281
  try {
2880
- await fs4.mkdir(this.statsPath, { recursive: true });
3282
+ await fs9.mkdir(this.statsPath, { recursive: true });
2881
3283
  const dailyPath = path5.join(this.statsPath, "daily.json");
2882
3284
  const totalPath = path5.join(this.statsPath, "total.json");
2883
3285
  await Promise.all([
2884
- fs4.writeFile(dailyPath, JSON.stringify(this.stats.byDate, null, 2)),
2885
- fs4.writeFile(totalPath, JSON.stringify({
3286
+ fs9.writeFile(dailyPath, JSON.stringify(this.stats.byDate, null, 2)),
3287
+ fs9.writeFile(totalPath, JSON.stringify({
2886
3288
  totalInput: this.stats.totalInput,
2887
3289
  totalOutput: this.stats.totalOutput
2888
3290
  }))
@@ -4591,15 +4993,15 @@ async function loadProjectContext(workingDirectory) {
4591
4993
  devStandards: ""
4592
4994
  };
4593
4995
  try {
4594
- context.agentsMd = await fs4.readFile(path5.join(workingDirectory, "AGENTS.md"), "utf-8");
4996
+ context.agentsMd = await fs9.readFile(path5.join(workingDirectory, "AGENTS.md"), "utf-8");
4595
4997
  } catch {
4596
4998
  }
4597
4999
  try {
4598
- context.configYaml = await fs4.readFile(path5.join(workingDirectory, "openspec", "config.yaml"), "utf-8");
5000
+ context.configYaml = await fs9.readFile(path5.join(workingDirectory, "openspec", "config.yaml"), "utf-8");
4599
5001
  } catch {
4600
5002
  }
4601
5003
  try {
4602
- context.devStandards = await fs4.readFile(path5.join(workingDirectory, ".sf-cli", "norms", "devstanded.md"), "utf-8");
5004
+ context.devStandards = await fs9.readFile(path5.join(workingDirectory, ".sf-cli", "norms", "devstanded.md"), "utf-8");
4603
5005
  } catch {
4604
5006
  }
4605
5007
  return context;
@@ -5562,19 +5964,19 @@ var WorkflowEngine = class {
5562
5964
  async loadProjectContext() {
5563
5965
  const agentsMdPath = path5.join(this.projectPath, "AGENTS.md");
5564
5966
  try {
5565
- this.projectContext = await fs4.readFile(agentsMdPath, "utf-8");
5967
+ this.projectContext = await fs9.readFile(agentsMdPath, "utf-8");
5566
5968
  } catch {
5567
5969
  this.projectContext = "";
5568
5970
  }
5569
5971
  const configPath = path5.join(this.openspecPath, "config.yaml");
5570
5972
  try {
5571
- this.projectConfig = await fs4.readFile(configPath, "utf-8");
5973
+ this.projectConfig = await fs9.readFile(configPath, "utf-8");
5572
5974
  } catch {
5573
5975
  this.projectConfig = "";
5574
5976
  }
5575
5977
  const devstandedPath = path5.join(this.projectPath, ".sf-cli", "norms", "devstanded.md");
5576
5978
  try {
5577
- this.devStandards = await fs4.readFile(devstandedPath, "utf-8");
5979
+ this.devStandards = await fs9.readFile(devstandedPath, "utf-8");
5578
5980
  } catch {
5579
5981
  this.devStandards = "";
5580
5982
  }
@@ -5603,7 +6005,7 @@ var WorkflowEngine = class {
5603
6005
  const specPath = this.getSpecFilePath();
5604
6006
  if (!specPath) return false;
5605
6007
  try {
5606
- await fs4.access(specPath);
6008
+ await fs9.access(specPath);
5607
6009
  return true;
5608
6010
  } catch {
5609
6011
  return false;
@@ -5837,11 +6239,11 @@ var WorkflowEngine = class {
5837
6239
  const workflows = [];
5838
6240
  const changesDir = path5.join(this.openspecPath, "changes");
5839
6241
  try {
5840
- const files = await fs4.readdir(changesDir);
6242
+ const files = await fs9.readdir(changesDir);
5841
6243
  for (const file of files) {
5842
6244
  if (!file.endsWith(".md") || file.includes("-spec.md")) continue;
5843
6245
  const filePath = path5.join(changesDir, file);
5844
- const content = await fs4.readFile(filePath, "utf-8");
6246
+ const content = await fs9.readFile(filePath, "utf-8");
5845
6247
  const state = this.parseChangeRecord(content);
5846
6248
  if (state && state.status === "running") {
5847
6249
  workflows.push(state);
@@ -5892,7 +6294,7 @@ var WorkflowEngine = class {
5892
6294
  }
5893
6295
  const statePath = path5.join(this.openspecPath, ".workflow-states", `${changeId}.json`);
5894
6296
  try {
5895
- const content = await fs4.readFile(statePath, "utf-8");
6297
+ const content = await fs9.readFile(statePath, "utf-8");
5896
6298
  this.state = JSON.parse(content, (key, value) => {
5897
6299
  if (key.endsWith("At") && typeof value === "string") {
5898
6300
  return new Date(value);
@@ -5905,7 +6307,7 @@ var WorkflowEngine = class {
5905
6307
  const changesDir = path5.join(this.openspecPath, "changes");
5906
6308
  const changeFile = path5.join(changesDir, `${changeId}.md`);
5907
6309
  try {
5908
- const content = await fs4.readFile(changeFile, "utf-8");
6310
+ const content = await fs9.readFile(changeFile, "utf-8");
5909
6311
  const parsed = this.parseChangeRecord(content);
5910
6312
  if (parsed && parsed.status === "running") {
5911
6313
  this.state = parsed;
@@ -5970,8 +6372,8 @@ var WorkflowEngine = class {
5970
6372
  const archiveDir = path5.join(changesDir, "archive");
5971
6373
  const changeFile = path5.join(changesDir, `${changeId}.md`);
5972
6374
  const archiveFile = path5.join(archiveDir, `${changeId}.md`);
5973
- await fs4.mkdir(archiveDir, { recursive: true });
5974
- await fs4.rename(changeFile, archiveFile).catch(() => {
6375
+ await fs9.mkdir(archiveDir, { recursive: true });
6376
+ await fs9.rename(changeFile, archiveFile).catch(() => {
5975
6377
  });
5976
6378
  this.state = null;
5977
6379
  this.snapshots.clear();
@@ -5997,15 +6399,15 @@ var WorkflowEngine = class {
5997
6399
  const archiveDir = path5.join(changesDir, "archive");
5998
6400
  const specDir = path5.join(this.openspecPath, "spec");
5999
6401
  const statesDir = path5.join(this.openspecPath, ".workflow-states");
6000
- await fs4.mkdir(archiveDir, { recursive: true });
6001
- await fs4.mkdir(specDir, { recursive: true });
6002
- await fs4.mkdir(statesDir, { recursive: true });
6402
+ await fs9.mkdir(archiveDir, { recursive: true });
6403
+ await fs9.mkdir(specDir, { recursive: true });
6404
+ await fs9.mkdir(statesDir, { recursive: true });
6003
6405
  }
6004
6406
  async restoreState() {
6005
6407
  const activePath = path5.join(this.openspecPath, ".workflow-active.json");
6006
6408
  let activeId = null;
6007
6409
  try {
6008
- const activeContent = await fs4.readFile(activePath, "utf-8");
6410
+ const activeContent = await fs9.readFile(activePath, "utf-8");
6009
6411
  const activeData = JSON.parse(activeContent);
6010
6412
  activeId = activeData.activeId;
6011
6413
  } catch {
@@ -6013,7 +6415,7 @@ var WorkflowEngine = class {
6013
6415
  if (activeId) {
6014
6416
  const statePath = path5.join(this.openspecPath, ".workflow-states", `${activeId}.json`);
6015
6417
  try {
6016
- const content = await fs4.readFile(statePath, "utf-8");
6418
+ const content = await fs9.readFile(statePath, "utf-8");
6017
6419
  this.state = JSON.parse(content, (key, value) => {
6018
6420
  if (key.endsWith("At") && typeof value === "string") {
6019
6421
  return new Date(value);
@@ -6026,7 +6428,7 @@ var WorkflowEngine = class {
6026
6428
  }
6027
6429
  const oldStatePath = path5.join(this.openspecPath, ".workflow-state.json");
6028
6430
  try {
6029
- const content = await fs4.readFile(oldStatePath, "utf-8");
6431
+ const content = await fs9.readFile(oldStatePath, "utf-8");
6030
6432
  this.state = JSON.parse(content, (key, value) => {
6031
6433
  if (key.endsWith("At") && typeof value === "string") {
6032
6434
  return new Date(value);
@@ -6035,7 +6437,7 @@ var WorkflowEngine = class {
6035
6437
  });
6036
6438
  if (this.state) {
6037
6439
  await this.saveState();
6038
- await fs4.unlink(oldStatePath).catch(() => {
6440
+ await fs9.unlink(oldStatePath).catch(() => {
6039
6441
  });
6040
6442
  }
6041
6443
  } catch (e) {
@@ -6049,16 +6451,16 @@ var WorkflowEngine = class {
6049
6451
  async saveState() {
6050
6452
  if (!this.state) return;
6051
6453
  const statesDir = path5.join(this.openspecPath, ".workflow-states");
6052
- await fs4.mkdir(statesDir, { recursive: true });
6454
+ await fs9.mkdir(statesDir, { recursive: true });
6053
6455
  const statePath = path5.join(statesDir, `${this.state.id}.json`);
6054
- await fs4.writeFile(statePath, JSON.stringify(this.state, null, 2));
6456
+ await fs9.writeFile(statePath, JSON.stringify(this.state, null, 2));
6055
6457
  const activePath = path5.join(this.openspecPath, ".workflow-active.json");
6056
- await fs4.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
6458
+ await fs9.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
6057
6459
  }
6058
6460
  async restoreSnapshots() {
6059
6461
  const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
6060
6462
  try {
6061
- const content = await fs4.readFile(snapshotsPath, "utf-8");
6463
+ const content = await fs9.readFile(snapshotsPath, "utf-8");
6062
6464
  const data = JSON.parse(content, (key, value) => {
6063
6465
  if (key === "timestamp" && typeof value === "string") {
6064
6466
  return new Date(value);
@@ -6075,7 +6477,7 @@ var WorkflowEngine = class {
6075
6477
  async saveSnapshots() {
6076
6478
  const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
6077
6479
  const data = Array.from(this.snapshots.values());
6078
- await fs4.writeFile(snapshotsPath, JSON.stringify(data, null, 2));
6480
+ await fs9.writeFile(snapshotsPath, JSON.stringify(data, null, 2));
6079
6481
  }
6080
6482
  async createSnapshot() {
6081
6483
  if (!this.state) return;
@@ -6096,7 +6498,7 @@ var WorkflowEngine = class {
6096
6498
  async createChangeRecord() {
6097
6499
  if (!this.state) return;
6098
6500
  const changePath = path5.join(this.openspecPath, "changes", `${this.state.id}.md`);
6099
- await fs4.writeFile(changePath, this.formatChangeRecord());
6501
+ await fs9.writeFile(changePath, this.formatChangeRecord());
6100
6502
  }
6101
6503
  async updateChangeRecord(status) {
6102
6504
  if (!this.state) return;
@@ -6104,7 +6506,7 @@ var WorkflowEngine = class {
6104
6506
  this.state.status = status;
6105
6507
  }
6106
6508
  const changePath = path5.join(this.openspecPath, "changes", `${this.state.id}.md`);
6107
- await fs4.writeFile(changePath, this.formatChangeRecord());
6509
+ await fs9.writeFile(changePath, this.formatChangeRecord());
6108
6510
  }
6109
6511
  formatChangeRecord() {
6110
6512
  if (!this.state) return "";
@@ -6168,7 +6570,7 @@ ${this.state.steps.map((s) => `- [${s.status === "completed" ? "x" : " "}] ${s.s
6168
6570
 
6169
6571
  ${this.state.artifacts.map((a) => `- ${a}`).join("\n") || "\u6682\u65E0"}
6170
6572
  `;
6171
- await fs4.writeFile(specPath, content);
6573
+ await fs9.writeFile(specPath, content);
6172
6574
  }
6173
6575
  };
6174
6576
  var ConfirmationRequiredError = class extends Error {
@@ -6217,11 +6619,11 @@ var NormsManager = class {
6217
6619
  const files = await this.collectProjectFiles(projectPath);
6218
6620
  for (const filePath of files.slice(0, MAX_FILES_TO_SCAN)) {
6219
6621
  try {
6220
- const stats = await fs4.stat(filePath);
6622
+ const stats = await fs9.stat(filePath);
6221
6623
  if (stats.size > MAX_FILE_SIZE) {
6222
6624
  continue;
6223
6625
  }
6224
- const content = await fs4.readFile(filePath, "utf-8");
6626
+ const content = await fs9.readFile(filePath, "utf-8");
6225
6627
  const patterns = await this.learnFromFile(filePath, content);
6226
6628
  result.patternsFound += patterns.length;
6227
6629
  result.filesScanned++;
@@ -6847,7 +7249,7 @@ ${response}`;
6847
7249
  const files = [];
6848
7250
  async function scan(dir) {
6849
7251
  try {
6850
- const entries = await fs4.readdir(dir, { withFileTypes: true });
7252
+ const entries = await fs9.readdir(dir, { withFileTypes: true });
6851
7253
  for (const entry of entries) {
6852
7254
  if (entry.isDirectory()) {
6853
7255
  if (!IGNORED_DIRS.includes(entry.name)) {
@@ -6890,8 +7292,8 @@ ${response}`;
6890
7292
  * 确保规范目录存在
6891
7293
  */
6892
7294
  async ensureNormsDir() {
6893
- await fs4.mkdir(this.config.normsDir, { recursive: true });
6894
- await fs4.mkdir(path5.join(this.config.normsDir, "weekly"), { recursive: true });
7295
+ await fs9.mkdir(this.config.normsDir, { recursive: true });
7296
+ await fs9.mkdir(path5.join(this.config.normsDir, "weekly"), { recursive: true });
6895
7297
  }
6896
7298
  /**
6897
7299
  * 加载已有规范
@@ -6899,7 +7301,7 @@ ${response}`;
6899
7301
  async loadStandards() {
6900
7302
  const standardsPath = path5.join(this.config.normsDir, "patterns.json");
6901
7303
  try {
6902
- const content = await fs4.readFile(standardsPath, "utf-8");
7304
+ const content = await fs9.readFile(standardsPath, "utf-8");
6903
7305
  const data = JSON.parse(content);
6904
7306
  for (const standard of data) {
6905
7307
  this.standards.set(standard.id, {
@@ -6917,7 +7319,7 @@ ${response}`;
6917
7319
  async loadWeights() {
6918
7320
  const weightsPath = path5.join(this.config.normsDir, "weights.json");
6919
7321
  try {
6920
- const content = await fs4.readFile(weightsPath, "utf-8");
7322
+ const content = await fs9.readFile(weightsPath, "utf-8");
6921
7323
  const data = JSON.parse(content);
6922
7324
  for (const weight of data) {
6923
7325
  this.weights.set(weight.standardId, {
@@ -6933,7 +7335,7 @@ ${response}`;
6933
7335
  */
6934
7336
  async saveStandards() {
6935
7337
  const standardsPath = path5.join(this.config.normsDir, "patterns.json");
6936
- await fs4.writeFile(
7338
+ await fs9.writeFile(
6937
7339
  standardsPath,
6938
7340
  JSON.stringify(Array.from(this.standards.values()), null, 2),
6939
7341
  "utf-8"
@@ -6944,7 +7346,7 @@ ${response}`;
6944
7346
  */
6945
7347
  async saveWeights() {
6946
7348
  const weightsPath = path5.join(this.config.normsDir, "weights.json");
6947
- await fs4.writeFile(
7349
+ await fs9.writeFile(
6948
7350
  weightsPath,
6949
7351
  JSON.stringify(Array.from(this.weights.values()), null, 2),
6950
7352
  "utf-8"
@@ -6972,7 +7374,7 @@ ${response}`;
6972
7374
  */
6973
7375
  async saveWeeklyReport(weekId, report) {
6974
7376
  const reportPath = path5.join(this.config.normsDir, "weekly", `${weekId}.json`);
6975
- await fs4.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
7377
+ await fs9.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
6976
7378
  }
6977
7379
  /**
6978
7380
  * 更新 devstanded.md
@@ -6981,7 +7383,7 @@ ${response}`;
6981
7383
  const standards = this.getEffectiveStandards();
6982
7384
  const content = this.generateDevStandedMd(standards);
6983
7385
  const mdPath = path5.join(this.config.normsDir, "devstanded.md");
6984
- await fs4.writeFile(mdPath, content, "utf-8");
7386
+ await fs9.writeFile(mdPath, content, "utf-8");
6985
7387
  }
6986
7388
  /**
6987
7389
  * 生成 devstanded.md 内容
@@ -7373,14 +7775,14 @@ ${summary}`,
7373
7775
  async persist() {
7374
7776
  if (!this.persistPath) return;
7375
7777
  const dir = path5.dirname(this.persistPath);
7376
- await fs4.mkdir(dir, { recursive: true });
7778
+ await fs9.mkdir(dir, { recursive: true });
7377
7779
  const data = {
7378
7780
  messages: this.state.messages,
7379
7781
  totalTokens: this.state.totalTokens,
7380
7782
  limit: this.state.limit,
7381
7783
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
7382
7784
  };
7383
- await fs4.writeFile(this.persistPath, JSON.stringify(data, null, 2), "utf-8");
7785
+ await fs9.writeFile(this.persistPath, JSON.stringify(data, null, 2), "utf-8");
7384
7786
  }
7385
7787
  /**
7386
7788
  * 加载持久化的上下文
@@ -7388,7 +7790,7 @@ ${summary}`,
7388
7790
  async loadPersistedContext() {
7389
7791
  if (!this.persistPath) return;
7390
7792
  try {
7391
- const content = await fs4.readFile(this.persistPath, "utf-8");
7793
+ const content = await fs9.readFile(this.persistPath, "utf-8");
7392
7794
  const data = JSON.parse(content);
7393
7795
  this.state.messages = data.messages || [];
7394
7796
  this.state.totalTokens = data.totalTokens || 0;
@@ -7605,7 +8007,7 @@ async function handleInit(args, ctx) {
7605
8007
  async function initProject(options = {}, workingDir) {
7606
8008
  const cwd = workingDir || process.cwd();
7607
8009
  try {
7608
- const stats = await fs4.stat(cwd);
8010
+ const stats = await fs9.stat(cwd);
7609
8011
  if (!stats.isDirectory()) {
7610
8012
  return {
7611
8013
  output: chalk9.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
@@ -7637,7 +8039,7 @@ async function initProject(options = {}, workingDir) {
7637
8039
  await normsManager.initialize();
7638
8040
  const scanResult = await normsManager.scanProject(cwd);
7639
8041
  const agentsContent = generateAgentsMd(projectInfo, scanResult);
7640
- await fs4.writeFile(agentsMdPath, agentsContent, "utf-8");
8042
+ await fs9.writeFile(agentsMdPath, agentsContent, "utf-8");
7641
8043
  await generateOpenSpecConfig(openspecDir, projectInfo);
7642
8044
  return {
7643
8045
  output: chalk9.green("\u2713 \u9879\u76EE\u521D\u59CB\u5316\u5B8C\u6210\n") + chalk9.gray(` \u9879\u76EE\u7C7B\u578B: ${projectInfo.type}
@@ -7678,7 +8080,7 @@ async function createSfCliDirectory(basePath) {
7678
8080
  "logs"
7679
8081
  ];
7680
8082
  for (const dir of dirs) {
7681
- await fs4.mkdir(path5.join(basePath, dir), { recursive: true });
8083
+ await fs9.mkdir(path5.join(basePath, dir), { recursive: true });
7682
8084
  }
7683
8085
  const config = {
7684
8086
  version: "1.0.0",
@@ -7686,22 +8088,22 @@ async function createSfCliDirectory(basePath) {
7686
8088
  yolo: false,
7687
8089
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
7688
8090
  };
7689
- await fs4.writeFile(
8091
+ await fs9.writeFile(
7690
8092
  path5.join(basePath, "config.json"),
7691
8093
  JSON.stringify(config, null, 2),
7692
8094
  "utf-8"
7693
8095
  );
7694
- await fs4.writeFile(
8096
+ await fs9.writeFile(
7695
8097
  path5.join(basePath, "agents", "registry.json"),
7696
8098
  JSON.stringify({ version: "1.0.0", agents: {} }, null, 2),
7697
8099
  "utf-8"
7698
8100
  );
7699
- await fs4.writeFile(
8101
+ await fs9.writeFile(
7700
8102
  path5.join(basePath, "skills", "registry.json"),
7701
8103
  JSON.stringify({ version: "1.0.0", skills: {} }, null, 2),
7702
8104
  "utf-8"
7703
8105
  );
7704
- await fs4.writeFile(
8106
+ await fs9.writeFile(
7705
8107
  path5.join(basePath, "health", "health.md"),
7706
8108
  generateHealthTemplate(),
7707
8109
  "utf-8"
@@ -7711,8 +8113,8 @@ async function createOpenSpecDirectory(basePath) {
7711
8113
  const changesDir = path5.join(basePath, "changes");
7712
8114
  const archiveDir = path5.join(changesDir, "archive");
7713
8115
  const specDir = path5.join(basePath, "spec");
7714
- await fs4.mkdir(archiveDir, { recursive: true });
7715
- await fs4.mkdir(specDir, { recursive: true });
8116
+ await fs9.mkdir(archiveDir, { recursive: true });
8117
+ await fs9.mkdir(specDir, { recursive: true });
7716
8118
  }
7717
8119
  async function analyzeProject(cwd) {
7718
8120
  const result = {
@@ -7737,7 +8139,7 @@ async function analyzeProject(cwd) {
7737
8139
  };
7738
8140
  const pkgPath = path5.join(cwd, "package.json");
7739
8141
  try {
7740
- const pkgContent = await fs4.readFile(pkgPath, "utf-8");
8142
+ const pkgContent = await fs9.readFile(pkgPath, "utf-8");
7741
8143
  const pkg = JSON.parse(pkgContent);
7742
8144
  result.name = pkg.name || result.name;
7743
8145
  result.dependencies = pkg.dependencies || {};
@@ -7820,7 +8222,7 @@ async function analyzeDirectoryStructure(cwd) {
7820
8222
  others: []
7821
8223
  };
7822
8224
  try {
7823
- const entries = await fs4.readdir(cwd, { withFileTypes: true });
8225
+ const entries = await fs9.readdir(cwd, { withFileTypes: true });
7824
8226
  for (const entry of entries) {
7825
8227
  if (!entry.isDirectory()) continue;
7826
8228
  const name = entry.name;
@@ -7865,7 +8267,7 @@ async function findEntryPoints(cwd) {
7865
8267
  }
7866
8268
  async function saveProjectAnalysis(sfCliDir, analysis) {
7867
8269
  const analysisPath = path5.join(sfCliDir, "cache", "analysis", "project-analysis.json");
7868
- await fs4.writeFile(
8270
+ await fs9.writeFile(
7869
8271
  analysisPath,
7870
8272
  JSON.stringify(analysis, null, 2),
7871
8273
  "utf-8"
@@ -7906,7 +8308,7 @@ architecture:
7906
8308
  # \u5F00\u53D1\u89C4\u8303 (\u4ECE\u4EE3\u7801\u4E2D\u5B66\u4E60)
7907
8309
  standards: []
7908
8310
  `;
7909
- await fs4.writeFile(configPath, content, "utf-8");
8311
+ await fs9.writeFile(configPath, content, "utf-8");
7910
8312
  }
7911
8313
  function generateAgentsMd(info, scanResult) {
7912
8314
  const techStackList = info.techStack.length > 0 ? info.techStack.map((t) => `| ${t} | \u2713 |`).join("\n") : "| \u5F85\u8BC6\u522B | - |";
@@ -8028,7 +8430,7 @@ function generateHealthTemplate() {
8028
8430
  }
8029
8431
  async function fileExists(filePath) {
8030
8432
  try {
8031
- await fs4.access(filePath);
8433
+ await fs9.access(filePath);
8032
8434
  return true;
8033
8435
  } catch {
8034
8436
  return false;
@@ -8193,6 +8595,36 @@ ${chalk9.yellow("\u793A\u4F8B:")}
8193
8595
 
8194
8596
  // src/commands/model.ts
8195
8597
  init_esm_shims();
8598
+ var LoadingIndicator = class {
8599
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
8600
+ frameIndex = 0;
8601
+ interval = null;
8602
+ message;
8603
+ constructor(message) {
8604
+ this.message = message;
8605
+ }
8606
+ start() {
8607
+ process.stdout.write("\x1B[?25l");
8608
+ this.interval = setInterval(() => {
8609
+ const frame = this.frames[this.frameIndex];
8610
+ process.stdout.write(`\r${chalk9.cyan(frame)} ${this.message}...`);
8611
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
8612
+ }, 80);
8613
+ }
8614
+ stop(finalMessage) {
8615
+ if (this.interval) {
8616
+ clearInterval(this.interval);
8617
+ this.interval = null;
8618
+ }
8619
+ process.stdout.write("\x1B[?25h");
8620
+ if (finalMessage) {
8621
+ process.stdout.write(`\r${finalMessage}
8622
+ `);
8623
+ } else {
8624
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
8625
+ }
8626
+ }
8627
+ };
8196
8628
  async function handleModel(args, ctx) {
8197
8629
  const subCommand = args[0];
8198
8630
  const configManager = ctx.configManager;
@@ -8292,9 +8724,12 @@ async function verifyCurrentModel(configManager, modelService) {
8292
8724
  output: chalk9.red(`\u2717 \u672A\u77E5\u6A21\u578B: ${currentModel}`)
8293
8725
  };
8294
8726
  }
8727
+ const loader = new LoadingIndicator(`\u9A8C\u8BC1 ${modelInfo.name} API Key`);
8728
+ loader.start();
8295
8729
  try {
8296
8730
  const adapter = createAdapter(modelInfo.provider);
8297
8731
  const isValid = await adapter.validateApiKey(apiKey);
8732
+ loader.stop();
8298
8733
  if (isValid) {
8299
8734
  return {
8300
8735
  output: chalk9.green(`\u2713 API Key \u9A8C\u8BC1\u6210\u529F
@@ -8306,6 +8741,7 @@ async function verifyCurrentModel(configManager, modelService) {
8306
8741
  };
8307
8742
  }
8308
8743
  } catch (error) {
8744
+ loader.stop();
8309
8745
  return {
8310
8746
  output: chalk9.red(`\u2717 \u9A8C\u8BC1\u5931\u8D25: ${error.message}`)
8311
8747
  };
@@ -9429,9 +9865,9 @@ async function handleFileReference(filePath, ctx) {
9429
9865
  const cwd = ctx.options.workingDirectory;
9430
9866
  const absolutePath = path5.isAbsolute(filePath) ? filePath : path5.join(cwd, filePath);
9431
9867
  try {
9432
- const stats = await fs4.stat(absolutePath);
9868
+ const stats = await fs9.stat(absolutePath);
9433
9869
  if (stats.isDirectory()) {
9434
- const files = await fs4.readdir(absolutePath);
9870
+ const files = await fs9.readdir(absolutePath);
9435
9871
  return {
9436
9872
  output: chalk9.cyan(`\u{1F4C1} ${filePath}/`) + chalk9.gray(`
9437
9873
  ${files.slice(0, 20).join("\n")}`) + (files.length > 20 ? chalk9.gray(`
@@ -9439,7 +9875,7 @@ ${files.slice(0, 20).join("\n")}`) + (files.length > 20 ? chalk9.gray(`
9439
9875
  contextUsed: 0
9440
9876
  };
9441
9877
  }
9442
- const content = await fs4.readFile(absolutePath, "utf-8");
9878
+ const content = await fs9.readFile(absolutePath, "utf-8");
9443
9879
  const lines = content.split("\n");
9444
9880
  ctx.contextManager.addMessage({
9445
9881
  role: "user",
@@ -9517,6 +9953,36 @@ async function executeShell(command, ctx) {
9517
9953
  // src/commands/natural.ts
9518
9954
  init_esm_shims();
9519
9955
  init_new();
9956
+ var LoadingIndicator3 = class {
9957
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
9958
+ frameIndex = 0;
9959
+ interval = null;
9960
+ message;
9961
+ constructor(message) {
9962
+ this.message = message;
9963
+ }
9964
+ start() {
9965
+ process.stdout.write("\x1B[?25l");
9966
+ this.interval = setInterval(() => {
9967
+ const frame = this.frames[this.frameIndex];
9968
+ process.stdout.write(`\r${chalk9.cyan(frame)} ${this.message}...`);
9969
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
9970
+ }, 80);
9971
+ }
9972
+ stop(finalMessage) {
9973
+ if (this.interval) {
9974
+ clearInterval(this.interval);
9975
+ this.interval = null;
9976
+ }
9977
+ process.stdout.write("\x1B[?25h");
9978
+ if (finalMessage) {
9979
+ process.stdout.write(`\r${finalMessage}
9980
+ `);
9981
+ } else {
9982
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
9983
+ }
9984
+ }
9985
+ };
9520
9986
  async function handleNaturalLanguage(input, ctx) {
9521
9987
  const trimmedInput = input.trim();
9522
9988
  const session = getActiveSession();
@@ -9536,6 +10002,8 @@ async function handleNaturalLanguage(input, ctx) {
9536
10002
  contextUsed: 0
9537
10003
  };
9538
10004
  }
10005
+ const loader = new LoadingIndicator3("AI \u601D\u8003\u4E2D");
10006
+ loader.start();
9539
10007
  try {
9540
10008
  const response = await ctx.modelService.sendMessage(
9541
10009
  [
@@ -9553,6 +10021,7 @@ async function handleNaturalLanguage(input, ctx) {
9553
10021
  maxTokens: 2e3
9554
10022
  }
9555
10023
  );
10024
+ loader.stop();
9556
10025
  ctx.contextManager.addMessage({
9557
10026
  role: "user",
9558
10027
  content: trimmedInput
@@ -9566,6 +10035,7 @@ async function handleNaturalLanguage(input, ctx) {
9566
10035
  contextUsed: response.usage?.totalTokens || 0
9567
10036
  };
9568
10037
  } catch (error) {
10038
+ loader.stop();
9569
10039
  const errorMessage = error.message;
9570
10040
  if (errorMessage.includes("\u672A\u914D\u7F6E") || errorMessage.includes("\u672A\u521D\u59CB\u5316")) {
9571
10041
  return {
@@ -9834,7 +10304,7 @@ var Completer = class {
9834
10304
  prefix = filePath.slice(lastSlash + 1);
9835
10305
  }
9836
10306
  try {
9837
- const entries = await fs4.readdir(dirPath, { withFileTypes: true });
10307
+ const entries = await fs9.readdir(dirPath, { withFileTypes: true });
9838
10308
  const matches = [];
9839
10309
  for (const entry of entries) {
9840
10310
  if (entry.name.startsWith(prefix)) {