@nick848/sf-cli 1.0.21 → 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/cli/index.js CHANGED
@@ -244,9 +244,10 @@ var init_base = __esm({
244
244
  }
245
245
  /**
246
246
  * 获取超时时间
247
+ * 默认 5 分钟,对于复杂的代码生成任务足够
247
248
  */
248
249
  getTimeout() {
249
- return this.config?.timeout || 6e4;
250
+ return this.config?.timeout || 3e5;
250
251
  }
251
252
  /**
252
253
  * 获取重试次数
@@ -1057,9 +1058,12 @@ async function verifyCurrentModel(configManager, modelService) {
1057
1058
  output: chalk9__default.default.red(`\u2717 \u672A\u77E5\u6A21\u578B: ${currentModel}`)
1058
1059
  };
1059
1060
  }
1061
+ const loader = new LoadingIndicator(`\u9A8C\u8BC1 ${modelInfo.name} API Key`);
1062
+ loader.start();
1060
1063
  try {
1061
1064
  const adapter = createAdapter(modelInfo.provider);
1062
1065
  const isValid = await adapter.validateApiKey(apiKey);
1066
+ loader.stop();
1063
1067
  if (isValid) {
1064
1068
  return {
1065
1069
  output: chalk9__default.default.green(`\u2713 API Key \u9A8C\u8BC1\u6210\u529F
@@ -1071,6 +1075,7 @@ async function verifyCurrentModel(configManager, modelService) {
1071
1075
  };
1072
1076
  }
1073
1077
  } catch (error) {
1078
+ loader.stop();
1074
1079
  return {
1075
1080
  output: chalk9__default.default.red(`\u2717 \u9A8C\u8BC1\u5931\u8D25: ${error.message}`)
1076
1081
  };
@@ -1192,12 +1197,42 @@ function createSpinner(message) {
1192
1197
  stop: () => process.stdout.write(" ".repeat(message.length + 10) + "\r")
1193
1198
  };
1194
1199
  }
1195
- var model_default;
1200
+ var LoadingIndicator, model_default;
1196
1201
  var init_model2 = __esm({
1197
1202
  "src/commands/model.ts"() {
1198
1203
  init_cjs_shims();
1199
1204
  init_adapters();
1200
1205
  init_model();
1206
+ LoadingIndicator = class {
1207
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1208
+ frameIndex = 0;
1209
+ interval = null;
1210
+ message;
1211
+ constructor(message) {
1212
+ this.message = message;
1213
+ }
1214
+ start() {
1215
+ process.stdout.write("\x1B[?25l");
1216
+ this.interval = setInterval(() => {
1217
+ const frame = this.frames[this.frameIndex];
1218
+ process.stdout.write(`\r${chalk9__default.default.cyan(frame)} ${this.message}...`);
1219
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
1220
+ }, 80);
1221
+ }
1222
+ stop(finalMessage) {
1223
+ if (this.interval) {
1224
+ clearInterval(this.interval);
1225
+ this.interval = null;
1226
+ }
1227
+ process.stdout.write("\x1B[?25h");
1228
+ if (finalMessage) {
1229
+ process.stdout.write(`\r${finalMessage}
1230
+ `);
1231
+ } else {
1232
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
1233
+ }
1234
+ }
1235
+ };
1201
1236
  model_default = selectModel;
1202
1237
  }
1203
1238
  });
@@ -1459,7 +1494,10 @@ async function executeWorkflow(ctx) {
1459
1494
  if (activeSession.phase === "context") {
1460
1495
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 1/9: \u9879\u76EE\u4E0A\u4E0B\u6587\u83B7\u53D6 \u2501\u2501\u2501"));
1461
1496
  lines.push("");
1497
+ const loader = new LoadingIndicator2("\u8BFB\u53D6\u9879\u76EE\u914D\u7F6E");
1498
+ loader.start();
1462
1499
  activeSession.context = await readProjectContext(ctx.options.workingDirectory);
1500
+ loader.stop();
1463
1501
  lines.push(chalk9__default.default.gray(` \u9879\u76EE: ${activeSession.context.name}`));
1464
1502
  lines.push(chalk9__default.default.gray(` \u7C7B\u578B: ${activeSession.context.type}`));
1465
1503
  lines.push(chalk9__default.default.gray(` \u6846\u67B6: ${activeSession.context.framework || "\u672A\u8BC6\u522B"}`));
@@ -1549,7 +1587,7 @@ ${resource.analysis}`;
1549
1587
  lines.push("");
1550
1588
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 5/9: BDD \u573A\u666F\u62C6\u89E3 \u2501\u2501\u2501"));
1551
1589
  lines.push("");
1552
- const loader = new LoadingIndicator("AI \u6B63\u5728\u751F\u6210 BDD \u573A\u666F");
1590
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u751F\u6210 BDD \u573A\u666F");
1553
1591
  loader.start();
1554
1592
  try {
1555
1593
  activeSession.bddScenarios = await generateBDDScenariosWithAI(
@@ -1584,7 +1622,7 @@ ${resource.analysis}`;
1584
1622
  lines.push("");
1585
1623
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 6/9: OpenSpec \u89C4\u683C \u2501\u2501\u2501"));
1586
1624
  lines.push("");
1587
- const loader = new LoadingIndicator("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
1625
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
1588
1626
  loader.start();
1589
1627
  try {
1590
1628
  activeSession.specItems = await generateSpecItemsWithAI(
@@ -1753,31 +1791,85 @@ async function handleWorkflowInput(input, ctx) {
1753
1791
  activeSession.phase = "analysis";
1754
1792
  return executeWorkflow(ctx);
1755
1793
  }
1756
- if (activeSession.phase === "spec") {
1794
+ if (activeSession.phase === "spec" || activeSession.specFeedbackState === "waiting_confirm") {
1757
1795
  if (trimmed === "y" || trimmed === "yes" || trimmed === "\u786E\u8BA4") {
1796
+ activeSession.specFeedbackState = void 0;
1758
1797
  activeSession.phase = "tdd";
1759
1798
  return executeWorkflow(ctx);
1760
1799
  }
1761
- if (trimmed === "n" || trimmed === "no" || trimmed === "\u91CD\u65B0") {
1762
- activeSession.bddScenarios = generateBDDScenarios(
1763
- activeSession.refinedRequirement,
1764
- activeSession.context,
1765
- activeSession.clarificationQuestions,
1766
- activeSession.referenceResources
1767
- );
1768
- activeSession.specItems = generateSpecItems(
1769
- activeSession.refinedRequirement,
1770
- activeSession.context,
1771
- activeSession.bddScenarios,
1772
- activeSession.clarificationQuestions,
1773
- activeSession.referenceResources
1774
- );
1800
+ if (trimmed === "n" || trimmed === "no" || trimmed === "\u4E0D\u6EE1\u610F" || trimmed === "\u91CD\u65B0") {
1801
+ activeSession.specFeedbackState = "collecting_feedback";
1802
+ if (!activeSession.specFeedbacks) {
1803
+ activeSession.specFeedbacks = [];
1804
+ }
1805
+ return {
1806
+ output: chalk9__default.default.yellow("\n\u{1F4DD} \u8BF7\u8F93\u5165\u60A8\u5BF9\u89C4\u683C\u4E0D\u6EE1\u610F\u7684\u5730\u65B9:") + chalk9__default.default.gray("\n - \u9057\u6F0F\u7684\u529F\u80FD\u70B9") + chalk9__default.default.gray("\n - \u4E0D\u5408\u7406\u7684\u62C6\u5206") + chalk9__default.default.gray("\n - \u9700\u8981\u8865\u5145\u7684\u7EC6\u8282") + chalk9__default.default.gray("\n - \u5176\u4ED6\u6539\u8FDB\u5EFA\u8BAE") + chalk9__default.default.cyan('\n\n\u8F93\u5165\u5B8C\u6210\u540E\uFF0C\u8F93\u5165\u7A7A\u884C\u6216 "done" \u7ED3\u675F\u8F93\u5165')
1807
+ };
1808
+ }
1809
+ }
1810
+ if (activeSession.specFeedbackState === "collecting_feedback") {
1811
+ if (trimmed === "" || trimmed === "done" || trimmed === "\u5B8C\u6210") {
1812
+ if (!activeSession.specFeedbacks || activeSession.specFeedbacks.length === 0) {
1813
+ return {
1814
+ output: chalk9__default.default.yellow('\u26A0\uFE0F \u8BF7\u81F3\u5C11\u8F93\u5165\u4E00\u6761\u53CD\u9988\u610F\u89C1\uFF0C\u6216\u8F93\u5165 "cancel" \u53D6\u6D88')
1815
+ };
1816
+ }
1817
+ activeSession.specFeedbackState = "feedback_received";
1818
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u6839\u636E\u53CD\u9988\u91CD\u65B0\u751F\u6210\u89C4\u683C");
1819
+ loader.start();
1820
+ try {
1821
+ activeSession.specItems = await generateSpecItemsWithFeedback(
1822
+ activeSession.refinedRequirement,
1823
+ activeSession.context,
1824
+ activeSession.bddScenarios,
1825
+ activeSession.clarificationQuestions,
1826
+ activeSession.referenceResources,
1827
+ activeSession.specFeedbacks,
1828
+ ctx
1829
+ );
1830
+ loader.stop(chalk9__default.default.green(" \u2713 \u89C4\u683C\u5DF2\u6839\u636E\u53CD\u9988\u91CD\u65B0\u751F\u6210"));
1831
+ } catch (error) {
1832
+ loader.stop(chalk9__default.default.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840\u65B9\u6CD5\u91CD\u65B0\u751F\u6210"));
1833
+ activeSession.specItems = generateSpecItems(
1834
+ activeSession.refinedRequirement,
1835
+ activeSession.context,
1836
+ activeSession.bddScenarios,
1837
+ activeSession.clarificationQuestions,
1838
+ activeSession.referenceResources
1839
+ );
1840
+ }
1775
1841
  const specPath = await saveSpecFile(ctx.options.workingDirectory, activeSession);
1842
+ activeSession.specFeedbackState = "waiting_confirm";
1843
+ const lines = [];
1844
+ lines.push(chalk9__default.default.cyan("\n\u2501\u2501\u2501 \u66F4\u65B0\u540E\u7684\u4EFB\u52A1\u6982\u89C8 \u2501\u2501\u2501"));
1845
+ for (const item of activeSession.specItems.slice(0, 8)) {
1846
+ const icon = item.priority === "high" ? "\u{1F534}" : item.priority === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
1847
+ lines.push(chalk9__default.default.gray(` ${icon} [${item.id}] ${item.title}`));
1848
+ }
1849
+ if (activeSession.specItems.length > 8) {
1850
+ lines.push(chalk9__default.default.gray(` ... \u5171 ${activeSession.specItems.length} \u4E2A\u4EFB\u52A1`));
1851
+ }
1776
1852
  return {
1777
- output: chalk9__default.default.cyan("\u{1F504} \u89C4\u683C\u5DF2\u91CD\u65B0\u751F\u6210") + chalk9__default.default.gray(`
1778
- \u8DEF\u5F84: ${specPath}`) + chalk9__default.default.yellow("\n\n\u8BF7\u786E\u8BA4:") + chalk9__default.default.green("\n y - \u786E\u8BA4\u89C4\u683C") + chalk9__default.default.red("\n n - \u518D\u6B21\u91CD\u65B0\u751F\u6210")
1853
+ output: lines.join("\n") + chalk9__default.default.green(`
1854
+
1855
+ \u2713 \u89C4\u683C\u6587\u4EF6\u5DF2\u66F4\u65B0`) + chalk9__default.default.gray(`
1856
+ \u8DEF\u5F84: ${specPath}`) + chalk9__default.default.yellow("\n\n\u8BF7\u786E\u8BA4:") + chalk9__default.default.green("\n y - \u786E\u8BA4\u89C4\u683C") + chalk9__default.default.red("\n n - \u4ECD\u6709\u95EE\u9898\uFF0C\u7EE7\u7EED\u53CD\u9988")
1779
1857
  };
1780
1858
  }
1859
+ if (trimmed === "cancel" || trimmed === "\u53D6\u6D88") {
1860
+ activeSession.specFeedbackState = "waiting_confirm";
1861
+ activeSession.specFeedbacks = [];
1862
+ return {
1863
+ output: chalk9__default.default.gray("\u5DF2\u53D6\u6D88\u53CD\u9988\uFF0C\u8FD4\u56DE\u89C4\u683C\u786E\u8BA4") + chalk9__default.default.yellow("\n\n\u8BF7\u786E\u8BA4:") + chalk9__default.default.green("\n y - \u786E\u8BA4\u89C4\u683C") + chalk9__default.default.red("\n n - \u4E0D\u6EE1\u610F\uFF0C\u91CD\u65B0\u751F\u6210")
1864
+ };
1865
+ }
1866
+ if (!activeSession.specFeedbacks) {
1867
+ activeSession.specFeedbacks = [];
1868
+ }
1869
+ activeSession.specFeedbacks.push(trimmed);
1870
+ return {
1871
+ output: chalk9__default.default.gray(`\u5DF2\u8BB0\u5F55\u53CD\u9988 #${activeSession.specFeedbacks.length}: `) + chalk9__default.default.white(trimmed.slice(0, 50)) + chalk9__default.default.gray('\n\u7EE7\u7EED\u8F93\u5165\uFF0C\u6216\u8F93\u5165 "done" \u5B8C\u6210\u53CD\u9988')
1872
+ };
1781
1873
  }
1782
1874
  if (activeSession.phase === "develop") {
1783
1875
  if (trimmed === "continue" || trimmed === "\u7EE7\u7EED" || trimmed === "done" || trimmed === "\u5B8C\u6210") {
@@ -1936,7 +2028,7 @@ async function executeDevelopment(ctx, session) {
1936
2028
  if (item.title.includes("\u6D4B\u8BD5") || item.title.includes("test")) {
1937
2029
  continue;
1938
2030
  }
1939
- const loader = new LoadingIndicator(`${prefix} \u751F\u6210: ${item.title.slice(0, 20)}...`);
2031
+ const loader = new LoadingIndicator2(`${prefix} \u751F\u6210: ${item.title.slice(0, 20)}...`);
1940
2032
  loader.start();
1941
2033
  try {
1942
2034
  const taskPrompt = buildTaskPrompt(session, item, i);
@@ -1969,8 +2061,8 @@ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
1969
2061
  maxTokens: 4e3,
1970
2062
  // 单个任务减少 token
1971
2063
  agent: "frontend-dev",
1972
- timeout: 12e4
1973
- // 2分钟超时
2064
+ timeout: 3e5
2065
+ // 5分钟超时,确保复杂代码有足够时间
1974
2066
  });
1975
2067
  const codeBlocks = parseCodeBlocks(response.content);
1976
2068
  if (codeBlocks.length > 0) {
@@ -2058,6 +2150,8 @@ async function executeReview(ctx, session) {
2058
2150
  const workingDir = ctx.options.workingDirectory;
2059
2151
  const issues = [];
2060
2152
  const suggestions = [];
2153
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u5BA1\u6838\u4EE3\u7801");
2154
+ loader.start();
2061
2155
  try {
2062
2156
  const codeContents = [];
2063
2157
  for (const file of session.implFiles) {
@@ -2120,7 +2214,9 @@ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
2120
2214
  const response = await ctx.modelService.sendMessage(messages, {
2121
2215
  temperature: 0.2,
2122
2216
  maxTokens: 2e3,
2123
- agent: "code-reviewer"
2217
+ agent: "code-reviewer",
2218
+ timeout: 18e4
2219
+ // 3分钟超时
2124
2220
  });
2125
2221
  const result = response.content;
2126
2222
  const passed = result.includes("\u5BA1\u6838\u901A\u8FC7") || result.includes("\u901A\u8FC7") || !result.includes("\u4E0D\u901A\u8FC7");
@@ -2142,8 +2238,10 @@ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
2142
2238
  const lines = result.split("\n").filter((l) => l.includes("\u5EFA\u8BAE") || l.includes("\u6539\u8FDB") || l.includes("\u4F18\u5316"));
2143
2239
  suggestions.push(...lines.map((l) => l.replace(/^[-*]\s*/, "").trim()).filter((l) => l));
2144
2240
  }
2241
+ loader.stop(passed ? chalk9__default.default.green(" \u2713 \u5BA1\u6838\u901A\u8FC7") : chalk9__default.default.yellow(" \u26A0 \u53D1\u73B0\u95EE\u9898"));
2145
2242
  return { passed, issues, suggestions };
2146
2243
  } catch (error) {
2244
+ loader.stop(chalk9__default.default.red(" \u2717 \u5BA1\u6838\u51FA\u9519"));
2147
2245
  suggestions.push(`\u5BA1\u6838\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
2148
2246
  return { passed: true, issues, suggestions };
2149
2247
  }
@@ -2349,7 +2447,8 @@ ${r.analysis}`).join("\n\n") : "\u65E0"}
2349
2447
  ], {
2350
2448
  temperature: 0.3,
2351
2449
  maxTokens: 4e3,
2352
- timeout: 12e4
2450
+ timeout: 3e5
2451
+ // 5分钟超时
2353
2452
  });
2354
2453
  try {
2355
2454
  const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
@@ -2557,7 +2656,7 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
2557
2656
  - \u96C6\u6210\u6D4B\u8BD5
2558
2657
 
2559
2658
  \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\u3002`;
2560
- const loader = new LoadingIndicator("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
2659
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
2561
2660
  loader.start();
2562
2661
  try {
2563
2662
  const response = await ctx.modelService.sendMessage([
@@ -2565,8 +2664,8 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
2565
2664
  ], {
2566
2665
  temperature: 0.3,
2567
2666
  maxTokens: 4e3,
2568
- timeout: 18e4
2569
- // 增加到3分钟
2667
+ timeout: 3e5
2668
+ // 5分钟超时
2570
2669
  });
2571
2670
  const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
2572
2671
  if (jsonMatch) {
@@ -2609,6 +2708,90 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
2609
2708
  return generateSpecItems(requirement, context, bddScenarios, questions, references);
2610
2709
  }
2611
2710
  }
2711
+ async function generateSpecItemsWithFeedback(requirement, context, bddScenarios, questions, references, feedbacks, ctx) {
2712
+ 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
2713
+
2714
+ ## \u9700\u6C42\u63CF\u8FF0
2715
+ ${requirement}
2716
+
2717
+ ## \u9879\u76EE\u4E0A\u4E0B\u6587
2718
+ - \u6280\u672F\u6808: ${context.techStack?.join(", ") || "TypeScript"}
2719
+ - \u6846\u67B6: ${context.framework || "\u672A\u6307\u5B9A"}
2720
+ ${context.devStandards ? `
2721
+ ## \u5F00\u53D1\u89C4\u8303\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09
2722
+ ${context.devStandards.slice(0, 2e3)}
2723
+ ` : ""}
2724
+
2725
+ ## \u4E4B\u524D\u7684 BDD \u573A\u666F
2726
+ ${bddScenarios.map((s) => `- Feature: ${s.feature}`).join("\n")}
2727
+
2728
+ ## \u7528\u6237\u53CD\u9988\uFF08\u5FC5\u987B\u89E3\u51B3\u8FD9\u4E9B\u95EE\u9898\uFF09
2729
+ ${feedbacks.map((f, i) => `${i + 1}. ${f}`).join("\n")}
2730
+
2731
+ ## \u8981\u6C42
2732
+ 1. **\u5FC5\u987B\u89E3\u51B3\u7528\u6237\u53CD\u9988\u4E2D\u7684\u6240\u6709\u95EE\u9898**
2733
+ 2. \u5982\u679C\u7528\u6237\u6307\u51FA\u9057\u6F0F\u7684\u529F\u80FD\u70B9\uFF0C\u8BF7\u8865\u5145\u76F8\u5173\u4EFB\u52A1
2734
+ 3. \u5982\u679C\u7528\u6237\u6307\u51FA\u62C6\u5206\u4E0D\u5408\u7406\uFF0C\u8BF7\u91CD\u65B0\u8C03\u6574\u7C92\u5EA6
2735
+ 4. \u5982\u679C\u7528\u6237\u9700\u8981\u66F4\u591A\u7EC6\u8282\uFF0C\u8BF7\u62C6\u5206\u5F97\u66F4\u7EC6\u81F4
2736
+ 5. \u6BCF\u4E2A\u4EFB\u52A1\u5E94\u8BE5\u5355\u4E00\u804C\u8D23\u30012-4\u5C0F\u65F6\u53EF\u5B8C\u6210
2737
+
2738
+ ## \u8F93\u51FA\u683C\u5F0F (JSON)
2739
+ \`\`\`json
2740
+ [
2741
+ {
2742
+ "id": "T001",
2743
+ "title": "\u4EFB\u52A1\u6807\u9898\uFF08\u7B80\u77ED\u660E\u786E\uFF09",
2744
+ "description": "\u8BE6\u7EC6\u63CF\u8FF0",
2745
+ "priority": "high",
2746
+ "acceptanceCriteria": ["\u9A8C\u6536\u6807\u51C61", "\u9A8C\u6536\u6807\u51C62"]
2747
+ }
2748
+ ]
2749
+ \`\`\`
2750
+
2751
+ \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\uFF0C\u786E\u4FDD\u89E3\u51B3\u4E86\u7528\u6237\u7684\u6240\u6709\u53CD\u9988\u3002`;
2752
+ try {
2753
+ const response = await ctx.modelService.sendMessage([
2754
+ { role: "user", content: prompt2 }
2755
+ ], {
2756
+ temperature: 0.3,
2757
+ maxTokens: 4e3,
2758
+ timeout: 3e5
2759
+ // 5分钟超时
2760
+ });
2761
+ const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
2762
+ if (jsonMatch) {
2763
+ try {
2764
+ const parsed = JSON.parse(jsonMatch[1].trim());
2765
+ return parsed.map((item, index) => ({
2766
+ id: item.id || `T${String(index + 1).padStart(3, "0")}`,
2767
+ title: item.title,
2768
+ description: item.description,
2769
+ priority: item.priority || "medium",
2770
+ files: [],
2771
+ tests: item.acceptanceCriteria || []
2772
+ }));
2773
+ } catch {
2774
+ }
2775
+ }
2776
+ try {
2777
+ const parsed = JSON.parse(response.content);
2778
+ if (Array.isArray(parsed)) {
2779
+ return parsed.map((item, index) => ({
2780
+ id: item.id || `T${String(index + 1).padStart(3, "0")}`,
2781
+ title: item.title,
2782
+ description: item.description,
2783
+ priority: item.priority || "medium",
2784
+ files: [],
2785
+ tests: item.acceptanceCriteria || []
2786
+ }));
2787
+ }
2788
+ } catch {
2789
+ }
2790
+ return generateSpecItems(requirement, context, bddScenarios, questions, references);
2791
+ } catch {
2792
+ return generateSpecItems(requirement, context, bddScenarios, questions, references);
2793
+ }
2794
+ }
2612
2795
  async function saveSpecFile(workingDir, session) {
2613
2796
  const specDir = path5__namespace.join(workingDir, "openspec", "changes");
2614
2797
  await fs5__namespace.mkdir(specDir, { recursive: true });
@@ -2675,6 +2858,16 @@ function formatSpecFile(session) {
2675
2858
  lines.push("");
2676
2859
  }
2677
2860
  }
2861
+ if (session.specFeedbacks && session.specFeedbacks.length > 0) {
2862
+ lines.push("## \u89C4\u683C\u8FED\u4EE3\u53CD\u9988");
2863
+ lines.push("");
2864
+ for (let i = 0; i < session.specFeedbacks.length; i++) {
2865
+ lines.push(`${i + 1}. ${session.specFeedbacks[i]}`);
2866
+ }
2867
+ lines.push("");
2868
+ lines.push("---");
2869
+ lines.push("");
2870
+ }
2678
2871
  lines.push("## \u4EFB\u52A1\u5217\u8868");
2679
2872
  lines.push("");
2680
2873
  for (const item of session.specItems) {
@@ -2695,7 +2888,7 @@ async function generateTests(workingDir, session, ctx) {
2695
2888
  for (const scenario of session.bddScenarios) {
2696
2889
  const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
2697
2890
  const testPath = path5__namespace.join(testDir, `${testName}.test.ts`);
2698
- const loader = new LoadingIndicator(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
2891
+ const loader = new LoadingIndicator2(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
2699
2892
  loader.start();
2700
2893
  try {
2701
2894
  const content = await generateTestFileWithAI(scenario, session, ctx);
@@ -2750,7 +2943,9 @@ ${scenario.scenarios.map((s) => `
2750
2943
  { role: "user", content: prompt2 }
2751
2944
  ], {
2752
2945
  temperature: 0.3,
2753
- maxTokens: 4e3
2946
+ maxTokens: 4e3,
2947
+ timeout: 18e4
2948
+ // 3分钟超时
2754
2949
  });
2755
2950
  const codeMatch = response.content.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
2756
2951
  if (codeMatch) {
@@ -2849,7 +3044,10 @@ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
2849
3044
  const type = detectResourceType(url);
2850
3045
  let content = "";
2851
3046
  let analysis = "";
3047
+ const loader = new LoadingIndicator2(`\u83B7\u53D6 ${url.slice(0, 40)}...`);
3048
+ loader.start();
2852
3049
  try {
3050
+ loader.update("\u6B63\u5728\u83B7\u53D6\u7F51\u9875\u5185\u5BB9");
2853
3051
  const response = await fetch(url, {
2854
3052
  headers: {
2855
3053
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
@@ -2860,11 +3058,14 @@ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
2860
3058
  }
2861
3059
  content = await response.text();
2862
3060
  if (ctx.modelService.getCurrentModel()) {
3061
+ loader.update("\u6B63\u5728\u5206\u6790\u5185\u5BB9");
2863
3062
  analysis = await analyzeReferenceContent(url, content, type, ctx, projectContext);
2864
3063
  } else {
2865
3064
  analysis = extractBasicInfo(content, type);
2866
3065
  }
3066
+ loader.stop();
2867
3067
  } catch (error) {
3068
+ loader.stop();
2868
3069
  throw new Error(`\u65E0\u6CD5\u83B7\u53D6\u53C2\u8003\u8D44\u6E90: ${error.message}`);
2869
3070
  }
2870
3071
  return { url, type, content: content.slice(0, 1e4), analysis };
@@ -2933,14 +3134,16 @@ ${content.slice(0, 8e3)}
2933
3134
  \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
2934
3135
  \u6280\u672F\u5B9E\u73B0\u65B9\u6848\u7531\u9879\u76EE\u89C4\u8303\u51B3\u5B9A\uFF0C\u6B64\u5904\u4E0D\u6D89\u53CA\u3002
2935
3136
  `;
2936
- const loader = new LoadingIndicator("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
3137
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
2937
3138
  loader.start();
2938
3139
  try {
2939
3140
  const response = await ctx.modelService.sendMessage([
2940
3141
  { role: "user", content: prompt2 }
2941
3142
  ], {
2942
3143
  temperature: 0.3,
2943
- maxTokens: 4e3
3144
+ maxTokens: 4e3,
3145
+ timeout: 18e4
3146
+ // 3分钟超时
2944
3147
  });
2945
3148
  loader.stop(chalk9__default.default.green(" \u2713 \u5206\u6790\u5B8C\u6210"));
2946
3149
  return response.content;
@@ -2987,11 +3190,11 @@ function getActiveSession() {
2987
3190
  function clearActiveSession() {
2988
3191
  activeSession = null;
2989
3192
  }
2990
- var LoadingIndicator, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
3193
+ var LoadingIndicator2, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
2991
3194
  var init_new = __esm({
2992
3195
  "src/commands/new.ts"() {
2993
3196
  init_cjs_shims();
2994
- LoadingIndicator = class {
3197
+ LoadingIndicator2 = class {
2995
3198
  frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2996
3199
  frameIndex = 0;
2997
3200
  interval = null;
@@ -7161,6 +7364,36 @@ async function executeShell(command, ctx) {
7161
7364
  // src/commands/natural.ts
7162
7365
  init_cjs_shims();
7163
7366
  init_new();
7367
+ var LoadingIndicator3 = class {
7368
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7369
+ frameIndex = 0;
7370
+ interval = null;
7371
+ message;
7372
+ constructor(message) {
7373
+ this.message = message;
7374
+ }
7375
+ start() {
7376
+ process.stdout.write("\x1B[?25l");
7377
+ this.interval = setInterval(() => {
7378
+ const frame = this.frames[this.frameIndex];
7379
+ process.stdout.write(`\r${chalk9__default.default.cyan(frame)} ${this.message}...`);
7380
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
7381
+ }, 80);
7382
+ }
7383
+ stop(finalMessage) {
7384
+ if (this.interval) {
7385
+ clearInterval(this.interval);
7386
+ this.interval = null;
7387
+ }
7388
+ process.stdout.write("\x1B[?25h");
7389
+ if (finalMessage) {
7390
+ process.stdout.write(`\r${finalMessage}
7391
+ `);
7392
+ } else {
7393
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
7394
+ }
7395
+ }
7396
+ };
7164
7397
  async function handleNaturalLanguage(input, ctx) {
7165
7398
  const trimmedInput = input.trim();
7166
7399
  const session = getActiveSession();
@@ -7180,6 +7413,8 @@ async function handleNaturalLanguage(input, ctx) {
7180
7413
  contextUsed: 0
7181
7414
  };
7182
7415
  }
7416
+ const loader = new LoadingIndicator3("AI \u601D\u8003\u4E2D");
7417
+ loader.start();
7183
7418
  try {
7184
7419
  const response = await ctx.modelService.sendMessage(
7185
7420
  [
@@ -7197,6 +7432,7 @@ async function handleNaturalLanguage(input, ctx) {
7197
7432
  maxTokens: 2e3
7198
7433
  }
7199
7434
  );
7435
+ loader.stop();
7200
7436
  ctx.contextManager.addMessage({
7201
7437
  role: "user",
7202
7438
  content: trimmedInput
@@ -7210,6 +7446,7 @@ async function handleNaturalLanguage(input, ctx) {
7210
7446
  contextUsed: response.usage?.totalTokens || 0
7211
7447
  };
7212
7448
  } catch (error) {
7449
+ loader.stop();
7213
7450
  const errorMessage = error.message;
7214
7451
  if (errorMessage.includes("\u672A\u914D\u7F6E") || errorMessage.includes("\u672A\u521D\u59CB\u5316")) {
7215
7452
  return {