@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/cli/index.js CHANGED
@@ -7,7 +7,7 @@ var enquirer = require('enquirer');
7
7
  var child_process = require('child_process');
8
8
  var fs7 = require('fs');
9
9
  var path5 = require('path');
10
- var fs5 = require('fs/promises');
10
+ var fs4 = require('fs/promises');
11
11
  var commander = require('commander');
12
12
  var readline = require('readline');
13
13
  require('uuid');
@@ -39,7 +39,7 @@ function _interopNamespace(e) {
39
39
  var chalk9__default = /*#__PURE__*/_interopDefault(chalk9);
40
40
  var fs7__namespace = /*#__PURE__*/_interopNamespace(fs7);
41
41
  var path5__namespace = /*#__PURE__*/_interopNamespace(path5);
42
- var fs5__namespace = /*#__PURE__*/_interopNamespace(fs5);
42
+ var fs4__namespace = /*#__PURE__*/_interopNamespace(fs4);
43
43
  var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
44
44
  var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
45
45
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
@@ -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
+ }
1852
+ return {
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")
1857
+ };
1858
+ }
1859
+ if (trimmed === "cancel" || trimmed === "\u53D6\u6D88") {
1860
+ activeSession.specFeedbackState = "waiting_confirm";
1861
+ activeSession.specFeedbacks = [];
1776
1862
  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")
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")
1779
1864
  };
1780
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,28 +2028,17 @@ 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);
2035
+ const structureInfo = session.context?.projectStructure;
2036
+ const structureGuide = buildStructureGuide(structureInfo);
2037
+ const systemPrompt = buildCodeGenerationSystemPrompt(session, structureGuide);
1943
2038
  const messages = [
1944
2039
  {
1945
2040
  role: "system",
1946
- 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
1947
-
1948
- \u26A0\uFE0F \u91CD\u8981\u89C4\u5219\uFF1A
1949
- 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
1950
- 2. \u6280\u672F\u5B9E\u73B0\u5FC5\u987B\u4E25\u683C\u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u5F00\u53D1\u89C4\u8303
1951
- 3. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
1952
- 4. \u8FD4\u56DE\u683C\u5F0F\uFF1A\u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u4EE3\u7801 \`\`\` \u5305\u88F9
1953
-
1954
- \u9879\u76EE\u4FE1\u606F\uFF1A
1955
- - \u540D\u79F0: ${session.context?.name}
1956
- - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
1957
- - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
1958
-
1959
- ${session.context?.devStandards ? `\u3010\u5F00\u53D1\u89C4\u8303\u3011
1960
- ${session.context.devStandards.slice(0, 2e3)}` : ""}`
2041
+ content: systemPrompt
1961
2042
  },
1962
2043
  {
1963
2044
  role: "user",
@@ -1969,25 +2050,25 @@ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
1969
2050
  maxTokens: 4e3,
1970
2051
  // 单个任务减少 token
1971
2052
  agent: "frontend-dev",
1972
- timeout: 12e4
1973
- // 2分钟超时
2053
+ timeout: 3e5
2054
+ // 5分钟超时,确保复杂代码有足够时间
1974
2055
  });
1975
2056
  const codeBlocks = parseCodeBlocks(response.content);
1976
2057
  if (codeBlocks.length > 0) {
1977
2058
  for (const block of codeBlocks) {
1978
2059
  const filePath = path5__namespace.join(workingDir, block.filename);
1979
2060
  const dir = path5__namespace.dirname(filePath);
1980
- await fs5__namespace.mkdir(dir, { recursive: true });
1981
- await fs5__namespace.writeFile(filePath, block.code, "utf-8");
2061
+ await fs4__namespace.mkdir(dir, { recursive: true });
2062
+ await fs4__namespace.writeFile(filePath, block.code, "utf-8");
1982
2063
  files.push(block.filename);
1983
2064
  }
1984
2065
  loader.stop(chalk9__default.default.green(`${prefix} \u2713 ${item.title.slice(0, 25)} (${codeBlocks.length} \u4E2A\u6587\u4EF6)`));
1985
2066
  } else {
1986
2067
  const implDir = path5__namespace.join(workingDir, "src");
1987
- await fs5__namespace.mkdir(implDir, { recursive: true });
2068
+ await fs4__namespace.mkdir(implDir, { recursive: true });
1988
2069
  const fileName = `${item.title.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_")}.ts`;
1989
2070
  const filePath = path5__namespace.join(implDir, fileName);
1990
- await fs5__namespace.writeFile(filePath, `// TODO: ${item.title}
2071
+ await fs4__namespace.writeFile(filePath, `// TODO: ${item.title}
1991
2072
  // ${item.description}
1992
2073
  `, "utf-8");
1993
2074
  files.push(`src/${fileName}`);
@@ -2010,6 +2091,91 @@ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
2010
2091
  };
2011
2092
  }
2012
2093
  }
2094
+ function buildStructureGuide(structure) {
2095
+ if (!structure) {
2096
+ return `## \u9879\u76EE\u7ED3\u6784
2097
+ - \u7EC4\u4EF6\u653E\u5728 src/components/
2098
+ - \u9875\u9762\u653E\u5728 src/pages/
2099
+ - \u5DE5\u5177\u51FD\u6570\u653E\u5728 src/utils/`;
2100
+ }
2101
+ const lines = ["## \u9879\u76EE\u7ED3\u6784\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09"];
2102
+ if (structure.hasSrc) {
2103
+ lines.push(`- \u6E90\u7801\u76EE\u5F55: ${structure.srcDir}/`);
2104
+ }
2105
+ if (structure.hasPages) {
2106
+ lines.push(`- \u9875\u9762\u76EE\u5F55: ${structure.pagesDir}/ \uFF08\u9875\u9762\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
2107
+ } else {
2108
+ lines.push(`- \u9875\u9762\u76EE\u5F55: src/pages/ \uFF08\u9875\u9762\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
2109
+ }
2110
+ if (structure.hasComponents) {
2111
+ lines.push(`- \u7EC4\u4EF6\u76EE\u5F55: ${structure.componentsDir}/ \uFF08\u901A\u7528\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
2112
+ } else {
2113
+ lines.push(`- \u7EC4\u4EF6\u76EE\u5F55: src/components/ \uFF08\u901A\u7528\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
2114
+ }
2115
+ if (structure.hasApi) {
2116
+ lines.push(`- API \u76EE\u5F55: src/api/ \u6216 src/services/`);
2117
+ }
2118
+ if (structure.hasHooks) {
2119
+ lines.push(`- Hooks \u76EE\u5F55: src/hooks/`);
2120
+ }
2121
+ if (structure.hasStore) {
2122
+ lines.push(`- \u72B6\u6001\u7BA1\u7406\u76EE\u5F55: src/store/`);
2123
+ }
2124
+ if (structure.hasUtils) {
2125
+ lines.push(`- \u5DE5\u5177\u51FD\u6570\u76EE\u5F55: src/utils/`);
2126
+ }
2127
+ lines.push("");
2128
+ 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");
2129
+ return lines.join("\n");
2130
+ }
2131
+ function buildCodeGenerationSystemPrompt(session, structureGuide) {
2132
+ const framework = session.context?.framework || "React";
2133
+ const techStack = session.context?.techStack?.join(", ") || "TypeScript";
2134
+ const devStandards = session.context?.devStandards || "";
2135
+ const standardsText = devStandards.slice(0, 6e3);
2136
+ 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
2137
+
2138
+ ## \u26A0\uFE0F \u6838\u5FC3\u89C4\u5219\uFF08\u5FC5\u987B\u9075\u5B88\uFF09
2139
+
2140
+ 1. **\u8BED\u8A00**: \u5FC5\u987B\u4F7F\u7528 TypeScript (.tsx/.ts \u6587\u4EF6)
2141
+ 2. **\u6846\u67B6**: ${framework}
2142
+ 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
2143
+ 4. **\u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u6B63\u786E**\uFF1A\u4E25\u683C\u6309\u7167\u9879\u76EE\u7ED3\u6784\u653E\u7F6E\u6587\u4EF6
2144
+ 5. **\u5FC5\u987B\u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801**\uFF0C\u4E0D\u8981\u5199 TODO \u6216\u5360\u4F4D\u7B26
2145
+ 6. **\u5982\u679C\u4EFB\u52A1\u6D89\u53CA\u9875\u9762**\uFF0C\u5FC5\u987B\u751F\u6210\u9875\u9762\u7EC4\u4EF6
2146
+ 7. **\u5982\u679C\u4EFB\u52A1\u6D89\u53CA UI**\uFF0C\u5FC5\u987B\u751F\u6210\u5BF9\u5E94\u7684\u7EC4\u4EF6\u548C\u6837\u5F0F
2147
+
2148
+ ${structureGuide}
2149
+
2150
+ ## \u9879\u76EE\u4FE1\u606F
2151
+
2152
+ - \u540D\u79F0: ${session.context?.name || "\u672A\u547D\u540D\u9879\u76EE"}
2153
+ - \u6846\u67B6: ${framework}
2154
+ - \u6280\u672F\u6808: ${techStack}
2155
+
2156
+ ## \u5F00\u53D1\u89C4\u8303\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09
2157
+
2158
+ ${standardsText || `### \u9ED8\u8BA4\u89C4\u8303
2159
+ - \u4F7F\u7528\u51FD\u6570\u5F0F\u7EC4\u4EF6
2160
+ - \u4F7F\u7528 TypeScript \u7C7B\u578B\u5B9A\u4E49
2161
+ - \u4F7F\u7528 async/await \u5904\u7406\u5F02\u6B65
2162
+ - \u53D8\u91CF\u4F7F\u7528 camelCase\uFF0C\u7EC4\u4EF6\u4F7F\u7528 PascalCase
2163
+ - \u5BFC\u51FA\u4F7F\u7528 export default \u6216 export const`}
2164
+
2165
+ ## \u8F93\u51FA\u683C\u5F0F
2166
+
2167
+ \u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u5305\u88F9\uFF0C\u4F8B\u5982\uFF1A
2168
+
2169
+ \`\`\`src/pages/HomePage.tsx
2170
+ // \u4EE3\u7801\u5185\u5BB9
2171
+ \`\`\`
2172
+
2173
+ \`\`\`src/components/Button/Button.tsx
2174
+ // \u4EE3\u7801\u5185\u5BB9
2175
+ \`\`\`
2176
+
2177
+ \u73B0\u5728\u8BF7\u6839\u636E\u4EFB\u52A1\u8981\u6C42\u751F\u6210\u4EE3\u7801\u3002`;
2178
+ }
2013
2179
  function buildTaskPrompt(session, item, index) {
2014
2180
  const lines = [];
2015
2181
  lines.push(`## \u5F53\u524D\u4EFB\u52A1 (#${index + 1})`);
@@ -2058,11 +2224,13 @@ async function executeReview(ctx, session) {
2058
2224
  const workingDir = ctx.options.workingDirectory;
2059
2225
  const issues = [];
2060
2226
  const suggestions = [];
2227
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u5BA1\u6838\u4EE3\u7801");
2228
+ loader.start();
2061
2229
  try {
2062
2230
  const codeContents = [];
2063
2231
  for (const file of session.implFiles) {
2064
2232
  try {
2065
- const content = await fs5__namespace.readFile(path5__namespace.join(workingDir, file), "utf-8");
2233
+ const content = await fs4__namespace.readFile(path5__namespace.join(workingDir, file), "utf-8");
2066
2234
  codeContents.push(`// ${file}
2067
2235
  ${content}`);
2068
2236
  } catch {
@@ -2071,7 +2239,7 @@ ${content}`);
2071
2239
  const testContents = [];
2072
2240
  for (const file of session.testFiles) {
2073
2241
  try {
2074
- const content = await fs5__namespace.readFile(path5__namespace.join(workingDir, file), "utf-8");
2242
+ const content = await fs4__namespace.readFile(path5__namespace.join(workingDir, file), "utf-8");
2075
2243
  testContents.push(`// ${file}
2076
2244
  ${content}`);
2077
2245
  } catch {
@@ -2120,7 +2288,9 @@ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
2120
2288
  const response = await ctx.modelService.sendMessage(messages, {
2121
2289
  temperature: 0.2,
2122
2290
  maxTokens: 2e3,
2123
- agent: "code-reviewer"
2291
+ agent: "code-reviewer",
2292
+ timeout: 18e4
2293
+ // 3分钟超时
2124
2294
  });
2125
2295
  const result = response.content;
2126
2296
  const passed = result.includes("\u5BA1\u6838\u901A\u8FC7") || result.includes("\u901A\u8FC7") || !result.includes("\u4E0D\u901A\u8FC7");
@@ -2142,8 +2312,10 @@ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
2142
2312
  const lines = result.split("\n").filter((l) => l.includes("\u5EFA\u8BAE") || l.includes("\u6539\u8FDB") || l.includes("\u4F18\u5316"));
2143
2313
  suggestions.push(...lines.map((l) => l.replace(/^[-*]\s*/, "").trim()).filter((l) => l));
2144
2314
  }
2315
+ loader.stop(passed ? chalk9__default.default.green(" \u2713 \u5BA1\u6838\u901A\u8FC7") : chalk9__default.default.yellow(" \u26A0 \u53D1\u73B0\u95EE\u9898"));
2145
2316
  return { passed, issues, suggestions };
2146
2317
  } catch (error) {
2318
+ loader.stop(chalk9__default.default.red(" \u2717 \u5BA1\u6838\u51FA\u9519"));
2147
2319
  suggestions.push(`\u5BA1\u6838\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
2148
2320
  return { passed: true, issues, suggestions };
2149
2321
  }
@@ -2161,9 +2333,9 @@ async function readProjectContext(workingDir) {
2161
2333
  };
2162
2334
  const agentsPath = path5__namespace.join(workingDir, "AGENTS.md");
2163
2335
  try {
2164
- const stats = await fs5__namespace.stat(agentsPath);
2336
+ const stats = await fs4__namespace.stat(agentsPath);
2165
2337
  if (stats.size <= MAX_FILE_SIZE2) {
2166
- context.agentsMd = await fs5__namespace.readFile(agentsPath, "utf-8");
2338
+ context.agentsMd = await fs4__namespace.readFile(agentsPath, "utf-8");
2167
2339
  const nameMatch = context.agentsMd.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
2168
2340
  if (nameMatch) context.name = nameMatch[1];
2169
2341
  const typeMatch = context.agentsMd.match(/\|\s*项目类型\s*\|\s*([^\s|]+)/);
@@ -2177,9 +2349,9 @@ async function readProjectContext(workingDir) {
2177
2349
  }
2178
2350
  const configPath = path5__namespace.join(workingDir, "openspec", "config.yaml");
2179
2351
  try {
2180
- const stats = await fs5__namespace.stat(configPath);
2352
+ const stats = await fs4__namespace.stat(configPath);
2181
2353
  if (stats.size <= MAX_FILE_SIZE2) {
2182
- context.configYaml = await fs5__namespace.readFile(configPath, "utf-8");
2354
+ context.configYaml = await fs4__namespace.readFile(configPath, "utf-8");
2183
2355
  const nameMatch = context.configYaml.match(/name:\s*(.+)/);
2184
2356
  if (nameMatch) context.name = nameMatch[1].trim();
2185
2357
  const typeMatch = context.configYaml.match(/type:\s*(.+)/);
@@ -2200,14 +2372,173 @@ async function readProjectContext(workingDir) {
2200
2372
  }
2201
2373
  const devStandardsPath = path5__namespace.join(workingDir, ".sf-cli", "norms", "devstanded.md");
2202
2374
  try {
2203
- const stats = await fs5__namespace.stat(devStandardsPath);
2375
+ const stats = await fs4__namespace.stat(devStandardsPath);
2204
2376
  if (stats.size <= MAX_FILE_SIZE2) {
2205
- context.devStandards = await fs5__namespace.readFile(devStandardsPath, "utf-8");
2377
+ context.devStandards = await fs4__namespace.readFile(devStandardsPath, "utf-8");
2206
2378
  }
2207
2379
  } catch {
2208
2380
  }
2381
+ if (!context.framework) {
2382
+ const detectedFramework = await detectFramework2(workingDir);
2383
+ if (detectedFramework) {
2384
+ context.framework = detectedFramework;
2385
+ context.techStack = [detectedFramework, ...context.techStack.filter((t) => t !== detectedFramework)];
2386
+ }
2387
+ }
2388
+ if (!context.devStandards) {
2389
+ context.devStandards = await analyzeProjectForStandards(workingDir, context);
2390
+ }
2391
+ context.projectStructure = await analyzeProjectStructure(workingDir);
2209
2392
  return context;
2210
2393
  }
2394
+ async function detectFramework2(workingDir) {
2395
+ try {
2396
+ const pkgPath = path5__namespace.join(workingDir, "package.json");
2397
+ const pkgContent = await fs4__namespace.readFile(pkgPath, "utf-8");
2398
+ const pkg = JSON.parse(pkgContent);
2399
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2400
+ if (deps["react"] || deps["next"]) return "React";
2401
+ if (deps["vue"] || deps["nuxt"]) return "Vue";
2402
+ if (deps["angular"] || deps["@angular/core"]) return "Angular";
2403
+ if (deps["svelte"]) return "Svelte";
2404
+ if (deps["solid-js"]) return "Solid";
2405
+ if (deps["vite"] || deps["webpack"]) return "JavaScript";
2406
+ if (deps["typescript"]) return "TypeScript";
2407
+ return null;
2408
+ } catch {
2409
+ return null;
2410
+ }
2411
+ }
2412
+ async function analyzeProjectForStandards(workingDir, context) {
2413
+ const standards = [];
2414
+ standards.push(`# \u9879\u76EE\u5F00\u53D1\u89C4\u8303\uFF08\u81EA\u52A8\u751F\u6210\uFF09
2415
+ > \u9879\u76EE\u540D\u79F0: ${context.name}
2416
+ > \u6846\u67B6: ${context.framework || "\u672A\u68C0\u6D4B\u5230"}
2417
+ > \u6280\u672F\u6808: ${context.techStack.join(", ") || "\u672A\u68C0\u6D4B\u5230"}
2418
+ `);
2419
+ try {
2420
+ const tsConfigPath = path5__namespace.join(workingDir, "tsconfig.json");
2421
+ await fs4__namespace.access(tsConfigPath);
2422
+ standards.push("\n## \u8BED\u8A00\n- \u4F7F\u7528 TypeScript");
2423
+ } catch {
2424
+ standards.push("\n## \u8BED\u8A00\n- \u4F7F\u7528 JavaScript");
2425
+ }
2426
+ try {
2427
+ const srcDir = path5__namespace.join(workingDir, "src");
2428
+ const files = await listFilesDeep(srcDir);
2429
+ const hasCss = files.some((f) => f.endsWith(".css"));
2430
+ const hasScss = files.some((f) => f.endsWith(".scss") || f.endsWith(".sass"));
2431
+ const hasLess = files.some((f) => f.endsWith(".less"));
2432
+ const hasStyled = files.some((f) => f.includes(".styled.") || f.includes("styled-components"));
2433
+ if (hasScss) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 SCSS/Sass");
2434
+ else if (hasLess) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 Less");
2435
+ else if (hasStyled) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 styled-components");
2436
+ else if (hasCss) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 CSS");
2437
+ } catch {
2438
+ }
2439
+ try {
2440
+ const srcDir = path5__namespace.join(workingDir, "src");
2441
+ const entries = await fs4__namespace.readdir(srcDir, { withFileTypes: true });
2442
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
2443
+ if (dirs.includes("components")) standards.push("\n## \u76EE\u5F55\u7ED3\u6784\n- \u7EC4\u4EF6\u653E\u5728 src/components/");
2444
+ if (dirs.includes("pages") || dirs.includes("views")) standards.push("- \u9875\u9762\u653E\u5728 src/pages/ \u6216 src/views/");
2445
+ if (dirs.includes("hooks")) standards.push("- Hooks \u653E\u5728 src/hooks/");
2446
+ if (dirs.includes("utils")) standards.push("- \u5DE5\u5177\u51FD\u6570\u653E\u5728 src/utils/");
2447
+ if (dirs.includes("api") || dirs.includes("services")) standards.push("- API \u8C03\u7528\u653E\u5728 src/api/ \u6216 src/services/");
2448
+ if (dirs.includes("store") || dirs.includes("stores")) standards.push("- \u72B6\u6001\u7BA1\u7406\u653E\u5728 src/store/");
2449
+ } catch {
2450
+ }
2451
+ standards.push("\n## \u4EE3\u7801\u89C4\u8303");
2452
+ standards.push("- \u4F7F\u7528 ES6+ \u8BED\u6CD5");
2453
+ standards.push("- \u7EC4\u4EF6\u4F7F\u7528\u51FD\u6570\u5F0F\u5199\u6CD5");
2454
+ standards.push("- \u4F7F\u7528 async/await \u5904\u7406\u5F02\u6B65");
2455
+ standards.push("- \u53D8\u91CF\u4F7F\u7528 camelCase\uFF0C\u7EC4\u4EF6\u4F7F\u7528 PascalCase");
2456
+ standards.push("- \u5E38\u91CF\u4F7F\u7528 UPPER_SNAKE_CASE");
2457
+ return standards.join("\n");
2458
+ }
2459
+ async function listFilesDeep(dir, maxDepth = 3) {
2460
+ const files = [];
2461
+ async function scan(currentDir, depth) {
2462
+ if (depth > maxDepth) return;
2463
+ try {
2464
+ const entries = await fs4__namespace.readdir(currentDir, { withFileTypes: true });
2465
+ for (const entry of entries) {
2466
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
2467
+ const fullPath = path5__namespace.join(currentDir, entry.name);
2468
+ if (entry.isDirectory()) {
2469
+ await scan(fullPath, depth + 1);
2470
+ } else {
2471
+ files.push(fullPath);
2472
+ }
2473
+ }
2474
+ } catch {
2475
+ }
2476
+ }
2477
+ await scan(dir, 0);
2478
+ return files;
2479
+ }
2480
+ async function analyzeProjectStructure(workingDir) {
2481
+ const structure = {
2482
+ hasSrc: false,
2483
+ hasPages: false,
2484
+ hasComponents: false,
2485
+ hasApi: false,
2486
+ hasHooks: false,
2487
+ hasStore: false,
2488
+ hasUtils: false,
2489
+ srcDir: "src",
2490
+ pagesDir: "",
2491
+ componentsDir: ""
2492
+ };
2493
+ try {
2494
+ const srcDir = path5__namespace.join(workingDir, "src");
2495
+ try {
2496
+ const srcStat = await fs4__namespace.stat(srcDir);
2497
+ if (srcStat.isDirectory()) {
2498
+ structure.hasSrc = true;
2499
+ structure.srcDir = "src";
2500
+ const srcEntries = await fs4__namespace.readdir(srcDir, { withFileTypes: true });
2501
+ const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
2502
+ if (srcDirs.includes("pages")) {
2503
+ structure.hasPages = true;
2504
+ structure.pagesDir = "src/pages";
2505
+ } else if (srcDirs.includes("views")) {
2506
+ structure.hasPages = true;
2507
+ structure.pagesDir = "src/views";
2508
+ }
2509
+ if (srcDirs.includes("components")) {
2510
+ structure.hasComponents = true;
2511
+ structure.componentsDir = "src/components";
2512
+ }
2513
+ if (srcDirs.includes("api") || srcDirs.includes("services")) {
2514
+ structure.hasApi = true;
2515
+ }
2516
+ if (srcDirs.includes("hooks")) {
2517
+ structure.hasHooks = true;
2518
+ }
2519
+ if (srcDirs.includes("store") || srcDirs.includes("stores")) {
2520
+ structure.hasStore = true;
2521
+ }
2522
+ if (srcDirs.includes("utils")) {
2523
+ structure.hasUtils = true;
2524
+ }
2525
+ }
2526
+ } catch {
2527
+ const rootEntries = await fs4__namespace.readdir(workingDir, { withFileTypes: true });
2528
+ const rootDirs = rootEntries.filter((e) => e.isDirectory()).map((e) => e.name);
2529
+ if (rootDirs.includes("pages")) {
2530
+ structure.hasPages = true;
2531
+ structure.pagesDir = "pages";
2532
+ }
2533
+ if (rootDirs.includes("components")) {
2534
+ structure.hasComponents = true;
2535
+ structure.componentsDir = "components";
2536
+ }
2537
+ }
2538
+ } catch {
2539
+ }
2540
+ return structure;
2541
+ }
2211
2542
  function analyzeComplexity(requirement, context) {
2212
2543
  let score = 3;
2213
2544
  if (requirement.length > 100) score += 1;
@@ -2349,7 +2680,8 @@ ${r.analysis}`).join("\n\n") : "\u65E0"}
2349
2680
  ], {
2350
2681
  temperature: 0.3,
2351
2682
  maxTokens: 4e3,
2352
- timeout: 12e4
2683
+ timeout: 3e5
2684
+ // 5分钟超时
2353
2685
  });
2354
2686
  try {
2355
2687
  const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
@@ -2557,7 +2889,7 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
2557
2889
  - \u96C6\u6210\u6D4B\u8BD5
2558
2890
 
2559
2891
  \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\u3002`;
2560
- const loader = new LoadingIndicator("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
2892
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u62C6\u5206\u89C4\u683C");
2561
2893
  loader.start();
2562
2894
  try {
2563
2895
  const response = await ctx.modelService.sendMessage([
@@ -2565,8 +2897,8 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
2565
2897
  ], {
2566
2898
  temperature: 0.3,
2567
2899
  maxTokens: 4e3,
2568
- timeout: 18e4
2569
- // 增加到3分钟
2900
+ timeout: 3e5
2901
+ // 5分钟超时
2570
2902
  });
2571
2903
  const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
2572
2904
  if (jsonMatch) {
@@ -2609,12 +2941,96 @@ ${questions.filter((q) => q.answered).map((q) => `- ${q.question}: ${q.answer}`)
2609
2941
  return generateSpecItems(requirement, context, bddScenarios, questions, references);
2610
2942
  }
2611
2943
  }
2944
+ async function generateSpecItemsWithFeedback(requirement, context, bddScenarios, questions, references, feedbacks, ctx) {
2945
+ 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
2946
+
2947
+ ## \u9700\u6C42\u63CF\u8FF0
2948
+ ${requirement}
2949
+
2950
+ ## \u9879\u76EE\u4E0A\u4E0B\u6587
2951
+ - \u6280\u672F\u6808: ${context.techStack?.join(", ") || "TypeScript"}
2952
+ - \u6846\u67B6: ${context.framework || "\u672A\u6307\u5B9A"}
2953
+ ${context.devStandards ? `
2954
+ ## \u5F00\u53D1\u89C4\u8303\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09
2955
+ ${context.devStandards.slice(0, 2e3)}
2956
+ ` : ""}
2957
+
2958
+ ## \u4E4B\u524D\u7684 BDD \u573A\u666F
2959
+ ${bddScenarios.map((s) => `- Feature: ${s.feature}`).join("\n")}
2960
+
2961
+ ## \u7528\u6237\u53CD\u9988\uFF08\u5FC5\u987B\u89E3\u51B3\u8FD9\u4E9B\u95EE\u9898\uFF09
2962
+ ${feedbacks.map((f, i) => `${i + 1}. ${f}`).join("\n")}
2963
+
2964
+ ## \u8981\u6C42
2965
+ 1. **\u5FC5\u987B\u89E3\u51B3\u7528\u6237\u53CD\u9988\u4E2D\u7684\u6240\u6709\u95EE\u9898**
2966
+ 2. \u5982\u679C\u7528\u6237\u6307\u51FA\u9057\u6F0F\u7684\u529F\u80FD\u70B9\uFF0C\u8BF7\u8865\u5145\u76F8\u5173\u4EFB\u52A1
2967
+ 3. \u5982\u679C\u7528\u6237\u6307\u51FA\u62C6\u5206\u4E0D\u5408\u7406\uFF0C\u8BF7\u91CD\u65B0\u8C03\u6574\u7C92\u5EA6
2968
+ 4. \u5982\u679C\u7528\u6237\u9700\u8981\u66F4\u591A\u7EC6\u8282\uFF0C\u8BF7\u62C6\u5206\u5F97\u66F4\u7EC6\u81F4
2969
+ 5. \u6BCF\u4E2A\u4EFB\u52A1\u5E94\u8BE5\u5355\u4E00\u804C\u8D23\u30012-4\u5C0F\u65F6\u53EF\u5B8C\u6210
2970
+
2971
+ ## \u8F93\u51FA\u683C\u5F0F (JSON)
2972
+ \`\`\`json
2973
+ [
2974
+ {
2975
+ "id": "T001",
2976
+ "title": "\u4EFB\u52A1\u6807\u9898\uFF08\u7B80\u77ED\u660E\u786E\uFF09",
2977
+ "description": "\u8BE6\u7EC6\u63CF\u8FF0",
2978
+ "priority": "high",
2979
+ "acceptanceCriteria": ["\u9A8C\u6536\u6807\u51C61", "\u9A8C\u6536\u6807\u51C62"]
2980
+ }
2981
+ ]
2982
+ \`\`\`
2983
+
2984
+ \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\uFF0C\u786E\u4FDD\u89E3\u51B3\u4E86\u7528\u6237\u7684\u6240\u6709\u53CD\u9988\u3002`;
2985
+ try {
2986
+ const response = await ctx.modelService.sendMessage([
2987
+ { role: "user", content: prompt2 }
2988
+ ], {
2989
+ temperature: 0.3,
2990
+ maxTokens: 4e3,
2991
+ timeout: 3e5
2992
+ // 5分钟超时
2993
+ });
2994
+ const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
2995
+ if (jsonMatch) {
2996
+ try {
2997
+ const parsed = JSON.parse(jsonMatch[1].trim());
2998
+ return parsed.map((item, index) => ({
2999
+ id: item.id || `T${String(index + 1).padStart(3, "0")}`,
3000
+ title: item.title,
3001
+ description: item.description,
3002
+ priority: item.priority || "medium",
3003
+ files: [],
3004
+ tests: item.acceptanceCriteria || []
3005
+ }));
3006
+ } catch {
3007
+ }
3008
+ }
3009
+ try {
3010
+ const parsed = JSON.parse(response.content);
3011
+ if (Array.isArray(parsed)) {
3012
+ return parsed.map((item, index) => ({
3013
+ id: item.id || `T${String(index + 1).padStart(3, "0")}`,
3014
+ title: item.title,
3015
+ description: item.description,
3016
+ priority: item.priority || "medium",
3017
+ files: [],
3018
+ tests: item.acceptanceCriteria || []
3019
+ }));
3020
+ }
3021
+ } catch {
3022
+ }
3023
+ return generateSpecItems(requirement, context, bddScenarios, questions, references);
3024
+ } catch {
3025
+ return generateSpecItems(requirement, context, bddScenarios, questions, references);
3026
+ }
3027
+ }
2612
3028
  async function saveSpecFile(workingDir, session) {
2613
3029
  const specDir = path5__namespace.join(workingDir, "openspec", "changes");
2614
- await fs5__namespace.mkdir(specDir, { recursive: true });
3030
+ await fs4__namespace.mkdir(specDir, { recursive: true });
2615
3031
  const specPath = path5__namespace.join(specDir, `${session.id}-spec.md`);
2616
3032
  const content = formatSpecFile(session);
2617
- await fs5__namespace.writeFile(specPath, content, "utf-8");
3033
+ await fs4__namespace.writeFile(specPath, content, "utf-8");
2618
3034
  return specPath;
2619
3035
  }
2620
3036
  function formatSpecFile(session) {
@@ -2675,6 +3091,16 @@ function formatSpecFile(session) {
2675
3091
  lines.push("");
2676
3092
  }
2677
3093
  }
3094
+ if (session.specFeedbacks && session.specFeedbacks.length > 0) {
3095
+ lines.push("## \u89C4\u683C\u8FED\u4EE3\u53CD\u9988");
3096
+ lines.push("");
3097
+ for (let i = 0; i < session.specFeedbacks.length; i++) {
3098
+ lines.push(`${i + 1}. ${session.specFeedbacks[i]}`);
3099
+ }
3100
+ lines.push("");
3101
+ lines.push("---");
3102
+ lines.push("");
3103
+ }
2678
3104
  lines.push("## \u4EFB\u52A1\u5217\u8868");
2679
3105
  lines.push("");
2680
3106
  for (const item of session.specItems) {
@@ -2689,22 +3115,22 @@ function formatSpecFile(session) {
2689
3115
  }
2690
3116
  async function generateTests(workingDir, session, ctx) {
2691
3117
  const testDir = path5__namespace.join(workingDir, "tests");
2692
- await fs5__namespace.mkdir(testDir, { recursive: true });
3118
+ await fs4__namespace.mkdir(testDir, { recursive: true });
2693
3119
  const testFiles = [];
2694
3120
  if (ctx?.modelService) {
2695
3121
  for (const scenario of session.bddScenarios) {
2696
3122
  const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
2697
3123
  const testPath = path5__namespace.join(testDir, `${testName}.test.ts`);
2698
- const loader = new LoadingIndicator(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
3124
+ const loader = new LoadingIndicator2(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
2699
3125
  loader.start();
2700
3126
  try {
2701
3127
  const content = await generateTestFileWithAI(scenario, session, ctx);
2702
- await fs5__namespace.writeFile(testPath, content, "utf-8");
3128
+ await fs4__namespace.writeFile(testPath, content, "utf-8");
2703
3129
  testFiles.push(`tests/${testName}.test.ts`);
2704
3130
  loader.stop(chalk9__default.default.green(` \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210`));
2705
3131
  } catch {
2706
3132
  const content = generateTestFile(scenario, session);
2707
- await fs5__namespace.writeFile(testPath, content, "utf-8");
3133
+ await fs4__namespace.writeFile(testPath, content, "utf-8");
2708
3134
  testFiles.push(`tests/${testName}.test.ts`);
2709
3135
  loader.stop(chalk9__default.default.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840\u6D4B\u8BD5\u6A21\u677F"));
2710
3136
  }
@@ -2714,7 +3140,7 @@ async function generateTests(workingDir, session, ctx) {
2714
3140
  const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
2715
3141
  const testPath = path5__namespace.join(testDir, `${testName}.test.ts`);
2716
3142
  const content = generateTestFile(scenario, session);
2717
- await fs5__namespace.writeFile(testPath, content, "utf-8");
3143
+ await fs4__namespace.writeFile(testPath, content, "utf-8");
2718
3144
  testFiles.push(`tests/${testName}.test.ts`);
2719
3145
  }
2720
3146
  }
@@ -2750,7 +3176,9 @@ ${scenario.scenarios.map((s) => `
2750
3176
  { role: "user", content: prompt2 }
2751
3177
  ], {
2752
3178
  temperature: 0.3,
2753
- maxTokens: 4e3
3179
+ maxTokens: 4e3,
3180
+ timeout: 18e4
3181
+ // 3分钟超时
2754
3182
  });
2755
3183
  const codeMatch = response.content.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
2756
3184
  if (codeMatch) {
@@ -2806,7 +3234,7 @@ function generateTestFile(scenario, session) {
2806
3234
  async function archiveWorkflow(workingDir) {
2807
3235
  if (!activeSession) return;
2808
3236
  const archiveDir = path5__namespace.join(workingDir, "openspec", "spec");
2809
- await fs5__namespace.mkdir(archiveDir, { recursive: true });
3237
+ await fs4__namespace.mkdir(archiveDir, { recursive: true });
2810
3238
  const archivePath = path5__namespace.join(archiveDir, `${activeSession.id}.md`);
2811
3239
  const content = `# \u5F52\u6863: ${activeSession.requirement.slice(0, 50)}
2812
3240
 
@@ -2833,7 +3261,7 @@ ${activeSession.refinedRequirement}
2833
3261
 
2834
3262
  ${activeSession.testFiles.map((f) => `- ${f}`).join("\n") || "\u65E0"}
2835
3263
  `;
2836
- await fs5__namespace.writeFile(archivePath, content, "utf-8");
3264
+ await fs4__namespace.writeFile(archivePath, content, "utf-8");
2837
3265
  }
2838
3266
  function generateSessionId() {
2839
3267
  const timestamp = Date.now().toString(36);
@@ -2849,7 +3277,10 @@ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
2849
3277
  const type = detectResourceType(url);
2850
3278
  let content = "";
2851
3279
  let analysis = "";
3280
+ const loader = new LoadingIndicator2(`\u83B7\u53D6 ${url.slice(0, 40)}...`);
3281
+ loader.start();
2852
3282
  try {
3283
+ loader.update("\u6B63\u5728\u83B7\u53D6\u7F51\u9875\u5185\u5BB9");
2853
3284
  const response = await fetch(url, {
2854
3285
  headers: {
2855
3286
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
@@ -2860,11 +3291,14 @@ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
2860
3291
  }
2861
3292
  content = await response.text();
2862
3293
  if (ctx.modelService.getCurrentModel()) {
3294
+ loader.update("\u6B63\u5728\u5206\u6790\u5185\u5BB9");
2863
3295
  analysis = await analyzeReferenceContent(url, content, type, ctx, projectContext);
2864
3296
  } else {
2865
3297
  analysis = extractBasicInfo(content, type);
2866
3298
  }
3299
+ loader.stop();
2867
3300
  } catch (error) {
3301
+ loader.stop();
2868
3302
  throw new Error(`\u65E0\u6CD5\u83B7\u53D6\u53C2\u8003\u8D44\u6E90: ${error.message}`);
2869
3303
  }
2870
3304
  return { url, type, content: content.slice(0, 1e4), analysis };
@@ -2933,14 +3367,16 @@ ${content.slice(0, 8e3)}
2933
3367
  \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
3368
  \u6280\u672F\u5B9E\u73B0\u65B9\u6848\u7531\u9879\u76EE\u89C4\u8303\u51B3\u5B9A\uFF0C\u6B64\u5904\u4E0D\u6D89\u53CA\u3002
2935
3369
  `;
2936
- const loader = new LoadingIndicator("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
3370
+ const loader = new LoadingIndicator2("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
2937
3371
  loader.start();
2938
3372
  try {
2939
3373
  const response = await ctx.modelService.sendMessage([
2940
3374
  { role: "user", content: prompt2 }
2941
3375
  ], {
2942
3376
  temperature: 0.3,
2943
- maxTokens: 4e3
3377
+ maxTokens: 4e3,
3378
+ timeout: 18e4
3379
+ // 3分钟超时
2944
3380
  });
2945
3381
  loader.stop(chalk9__default.default.green(" \u2713 \u5206\u6790\u5B8C\u6210"));
2946
3382
  return response.content;
@@ -2987,11 +3423,11 @@ function getActiveSession() {
2987
3423
  function clearActiveSession() {
2988
3424
  activeSession = null;
2989
3425
  }
2990
- var LoadingIndicator, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
3426
+ var LoadingIndicator2, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
2991
3427
  var init_new = __esm({
2992
3428
  "src/commands/new.ts"() {
2993
3429
  init_cjs_shims();
2994
- LoadingIndicator = class {
3430
+ LoadingIndicator2 = class {
2995
3431
  frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2996
3432
  frameIndex = 0;
2997
3433
  interval = null;
@@ -3247,11 +3683,11 @@ var NormsManager = class {
3247
3683
  const files = await this.collectProjectFiles(projectPath);
3248
3684
  for (const filePath of files.slice(0, MAX_FILES_TO_SCAN)) {
3249
3685
  try {
3250
- const stats = await fs5__namespace.stat(filePath);
3686
+ const stats = await fs4__namespace.stat(filePath);
3251
3687
  if (stats.size > MAX_FILE_SIZE) {
3252
3688
  continue;
3253
3689
  }
3254
- const content = await fs5__namespace.readFile(filePath, "utf-8");
3690
+ const content = await fs4__namespace.readFile(filePath, "utf-8");
3255
3691
  const patterns = await this.learnFromFile(filePath, content);
3256
3692
  result.patternsFound += patterns.length;
3257
3693
  result.filesScanned++;
@@ -3877,7 +4313,7 @@ ${response}`;
3877
4313
  const files = [];
3878
4314
  async function scan(dir) {
3879
4315
  try {
3880
- const entries = await fs5__namespace.readdir(dir, { withFileTypes: true });
4316
+ const entries = await fs4__namespace.readdir(dir, { withFileTypes: true });
3881
4317
  for (const entry of entries) {
3882
4318
  if (entry.isDirectory()) {
3883
4319
  if (!IGNORED_DIRS.includes(entry.name)) {
@@ -3920,8 +4356,8 @@ ${response}`;
3920
4356
  * 确保规范目录存在
3921
4357
  */
3922
4358
  async ensureNormsDir() {
3923
- await fs5__namespace.mkdir(this.config.normsDir, { recursive: true });
3924
- await fs5__namespace.mkdir(path5__namespace.join(this.config.normsDir, "weekly"), { recursive: true });
4359
+ await fs4__namespace.mkdir(this.config.normsDir, { recursive: true });
4360
+ await fs4__namespace.mkdir(path5__namespace.join(this.config.normsDir, "weekly"), { recursive: true });
3925
4361
  }
3926
4362
  /**
3927
4363
  * 加载已有规范
@@ -3929,7 +4365,7 @@ ${response}`;
3929
4365
  async loadStandards() {
3930
4366
  const standardsPath = path5__namespace.join(this.config.normsDir, "patterns.json");
3931
4367
  try {
3932
- const content = await fs5__namespace.readFile(standardsPath, "utf-8");
4368
+ const content = await fs4__namespace.readFile(standardsPath, "utf-8");
3933
4369
  const data = JSON.parse(content);
3934
4370
  for (const standard of data) {
3935
4371
  this.standards.set(standard.id, {
@@ -3947,7 +4383,7 @@ ${response}`;
3947
4383
  async loadWeights() {
3948
4384
  const weightsPath = path5__namespace.join(this.config.normsDir, "weights.json");
3949
4385
  try {
3950
- const content = await fs5__namespace.readFile(weightsPath, "utf-8");
4386
+ const content = await fs4__namespace.readFile(weightsPath, "utf-8");
3951
4387
  const data = JSON.parse(content);
3952
4388
  for (const weight of data) {
3953
4389
  this.weights.set(weight.standardId, {
@@ -3963,7 +4399,7 @@ ${response}`;
3963
4399
  */
3964
4400
  async saveStandards() {
3965
4401
  const standardsPath = path5__namespace.join(this.config.normsDir, "patterns.json");
3966
- await fs5__namespace.writeFile(
4402
+ await fs4__namespace.writeFile(
3967
4403
  standardsPath,
3968
4404
  JSON.stringify(Array.from(this.standards.values()), null, 2),
3969
4405
  "utf-8"
@@ -3974,7 +4410,7 @@ ${response}`;
3974
4410
  */
3975
4411
  async saveWeights() {
3976
4412
  const weightsPath = path5__namespace.join(this.config.normsDir, "weights.json");
3977
- await fs5__namespace.writeFile(
4413
+ await fs4__namespace.writeFile(
3978
4414
  weightsPath,
3979
4415
  JSON.stringify(Array.from(this.weights.values()), null, 2),
3980
4416
  "utf-8"
@@ -4002,7 +4438,7 @@ ${response}`;
4002
4438
  */
4003
4439
  async saveWeeklyReport(weekId, report) {
4004
4440
  const reportPath = path5__namespace.join(this.config.normsDir, "weekly", `${weekId}.json`);
4005
- await fs5__namespace.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
4441
+ await fs4__namespace.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
4006
4442
  }
4007
4443
  /**
4008
4444
  * 更新 devstanded.md
@@ -4011,7 +4447,7 @@ ${response}`;
4011
4447
  const standards = this.getEffectiveStandards();
4012
4448
  const content = this.generateDevStandedMd(standards);
4013
4449
  const mdPath = path5__namespace.join(this.config.normsDir, "devstanded.md");
4014
- await fs5__namespace.writeFile(mdPath, content, "utf-8");
4450
+ await fs4__namespace.writeFile(mdPath, content, "utf-8");
4015
4451
  }
4016
4452
  /**
4017
4453
  * 生成 devstanded.md 内容
@@ -4150,7 +4586,7 @@ async function handleInit(args, ctx) {
4150
4586
  async function initProject(options = {}, workingDir) {
4151
4587
  const cwd = workingDir || process.cwd();
4152
4588
  try {
4153
- const stats = await fs5__namespace.stat(cwd);
4589
+ const stats = await fs4__namespace.stat(cwd);
4154
4590
  if (!stats.isDirectory()) {
4155
4591
  return {
4156
4592
  output: chalk9__default.default.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
@@ -4182,7 +4618,7 @@ async function initProject(options = {}, workingDir) {
4182
4618
  await normsManager.initialize();
4183
4619
  const scanResult = await normsManager.scanProject(cwd);
4184
4620
  const agentsContent = generateAgentsMd(projectInfo, scanResult);
4185
- await fs5__namespace.writeFile(agentsMdPath, agentsContent, "utf-8");
4621
+ await fs4__namespace.writeFile(agentsMdPath, agentsContent, "utf-8");
4186
4622
  await generateOpenSpecConfig(openspecDir, projectInfo);
4187
4623
  return {
4188
4624
  output: chalk9__default.default.green("\u2713 \u9879\u76EE\u521D\u59CB\u5316\u5B8C\u6210\n") + chalk9__default.default.gray(` \u9879\u76EE\u7C7B\u578B: ${projectInfo.type}
@@ -4223,7 +4659,7 @@ async function createSfCliDirectory(basePath) {
4223
4659
  "logs"
4224
4660
  ];
4225
4661
  for (const dir of dirs) {
4226
- await fs5__namespace.mkdir(path5__namespace.join(basePath, dir), { recursive: true });
4662
+ await fs4__namespace.mkdir(path5__namespace.join(basePath, dir), { recursive: true });
4227
4663
  }
4228
4664
  const config = {
4229
4665
  version: "1.0.0",
@@ -4231,22 +4667,22 @@ async function createSfCliDirectory(basePath) {
4231
4667
  yolo: false,
4232
4668
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4233
4669
  };
4234
- await fs5__namespace.writeFile(
4670
+ await fs4__namespace.writeFile(
4235
4671
  path5__namespace.join(basePath, "config.json"),
4236
4672
  JSON.stringify(config, null, 2),
4237
4673
  "utf-8"
4238
4674
  );
4239
- await fs5__namespace.writeFile(
4675
+ await fs4__namespace.writeFile(
4240
4676
  path5__namespace.join(basePath, "agents", "registry.json"),
4241
4677
  JSON.stringify({ version: "1.0.0", agents: {} }, null, 2),
4242
4678
  "utf-8"
4243
4679
  );
4244
- await fs5__namespace.writeFile(
4680
+ await fs4__namespace.writeFile(
4245
4681
  path5__namespace.join(basePath, "skills", "registry.json"),
4246
4682
  JSON.stringify({ version: "1.0.0", skills: {} }, null, 2),
4247
4683
  "utf-8"
4248
4684
  );
4249
- await fs5__namespace.writeFile(
4685
+ await fs4__namespace.writeFile(
4250
4686
  path5__namespace.join(basePath, "health", "health.md"),
4251
4687
  generateHealthTemplate(),
4252
4688
  "utf-8"
@@ -4256,8 +4692,8 @@ async function createOpenSpecDirectory(basePath) {
4256
4692
  const changesDir = path5__namespace.join(basePath, "changes");
4257
4693
  const archiveDir = path5__namespace.join(changesDir, "archive");
4258
4694
  const specDir = path5__namespace.join(basePath, "spec");
4259
- await fs5__namespace.mkdir(archiveDir, { recursive: true });
4260
- await fs5__namespace.mkdir(specDir, { recursive: true });
4695
+ await fs4__namespace.mkdir(archiveDir, { recursive: true });
4696
+ await fs4__namespace.mkdir(specDir, { recursive: true });
4261
4697
  }
4262
4698
  async function analyzeProject(cwd) {
4263
4699
  const result = {
@@ -4282,7 +4718,7 @@ async function analyzeProject(cwd) {
4282
4718
  };
4283
4719
  const pkgPath = path5__namespace.join(cwd, "package.json");
4284
4720
  try {
4285
- const pkgContent = await fs5__namespace.readFile(pkgPath, "utf-8");
4721
+ const pkgContent = await fs4__namespace.readFile(pkgPath, "utf-8");
4286
4722
  const pkg = JSON.parse(pkgContent);
4287
4723
  result.name = pkg.name || result.name;
4288
4724
  result.dependencies = pkg.dependencies || {};
@@ -4365,7 +4801,7 @@ async function analyzeDirectoryStructure(cwd) {
4365
4801
  others: []
4366
4802
  };
4367
4803
  try {
4368
- const entries = await fs5__namespace.readdir(cwd, { withFileTypes: true });
4804
+ const entries = await fs4__namespace.readdir(cwd, { withFileTypes: true });
4369
4805
  for (const entry of entries) {
4370
4806
  if (!entry.isDirectory()) continue;
4371
4807
  const name = entry.name;
@@ -4410,7 +4846,7 @@ async function findEntryPoints(cwd) {
4410
4846
  }
4411
4847
  async function saveProjectAnalysis(sfCliDir, analysis) {
4412
4848
  const analysisPath = path5__namespace.join(sfCliDir, "cache", "analysis", "project-analysis.json");
4413
- await fs5__namespace.writeFile(
4849
+ await fs4__namespace.writeFile(
4414
4850
  analysisPath,
4415
4851
  JSON.stringify(analysis, null, 2),
4416
4852
  "utf-8"
@@ -4451,7 +4887,7 @@ architecture:
4451
4887
  # \u5F00\u53D1\u89C4\u8303 (\u4ECE\u4EE3\u7801\u4E2D\u5B66\u4E60)
4452
4888
  standards: []
4453
4889
  `;
4454
- await fs5__namespace.writeFile(configPath, content, "utf-8");
4890
+ await fs4__namespace.writeFile(configPath, content, "utf-8");
4455
4891
  }
4456
4892
  function generateAgentsMd(info, scanResult) {
4457
4893
  const techStackList = info.techStack.length > 0 ? info.techStack.map((t) => `| ${t} | \u2713 |`).join("\n") : "| \u5F85\u8BC6\u522B | - |";
@@ -4573,7 +5009,7 @@ function generateHealthTemplate() {
4573
5009
  }
4574
5010
  async function fileExists(filePath) {
4575
5011
  try {
4576
- await fs5__namespace.access(filePath);
5012
+ await fs4__namespace.access(filePath);
4577
5013
  return true;
4578
5014
  } catch {
4579
5015
  return false;
@@ -5040,19 +5476,19 @@ var WorkflowEngine = class {
5040
5476
  async loadProjectContext() {
5041
5477
  const agentsMdPath = path5__namespace.join(this.projectPath, "AGENTS.md");
5042
5478
  try {
5043
- this.projectContext = await fs5__namespace.readFile(agentsMdPath, "utf-8");
5479
+ this.projectContext = await fs4__namespace.readFile(agentsMdPath, "utf-8");
5044
5480
  } catch {
5045
5481
  this.projectContext = "";
5046
5482
  }
5047
5483
  const configPath = path5__namespace.join(this.openspecPath, "config.yaml");
5048
5484
  try {
5049
- this.projectConfig = await fs5__namespace.readFile(configPath, "utf-8");
5485
+ this.projectConfig = await fs4__namespace.readFile(configPath, "utf-8");
5050
5486
  } catch {
5051
5487
  this.projectConfig = "";
5052
5488
  }
5053
5489
  const devstandedPath = path5__namespace.join(this.projectPath, ".sf-cli", "norms", "devstanded.md");
5054
5490
  try {
5055
- this.devStandards = await fs5__namespace.readFile(devstandedPath, "utf-8");
5491
+ this.devStandards = await fs4__namespace.readFile(devstandedPath, "utf-8");
5056
5492
  } catch {
5057
5493
  this.devStandards = "";
5058
5494
  }
@@ -5081,7 +5517,7 @@ var WorkflowEngine = class {
5081
5517
  const specPath = this.getSpecFilePath();
5082
5518
  if (!specPath) return false;
5083
5519
  try {
5084
- await fs5__namespace.access(specPath);
5520
+ await fs4__namespace.access(specPath);
5085
5521
  return true;
5086
5522
  } catch {
5087
5523
  return false;
@@ -5315,11 +5751,11 @@ var WorkflowEngine = class {
5315
5751
  const workflows = [];
5316
5752
  const changesDir = path5__namespace.join(this.openspecPath, "changes");
5317
5753
  try {
5318
- const files = await fs5__namespace.readdir(changesDir);
5754
+ const files = await fs4__namespace.readdir(changesDir);
5319
5755
  for (const file of files) {
5320
5756
  if (!file.endsWith(".md") || file.includes("-spec.md")) continue;
5321
5757
  const filePath = path5__namespace.join(changesDir, file);
5322
- const content = await fs5__namespace.readFile(filePath, "utf-8");
5758
+ const content = await fs4__namespace.readFile(filePath, "utf-8");
5323
5759
  const state = this.parseChangeRecord(content);
5324
5760
  if (state && state.status === "running") {
5325
5761
  workflows.push(state);
@@ -5370,7 +5806,7 @@ var WorkflowEngine = class {
5370
5806
  }
5371
5807
  const statePath = path5__namespace.join(this.openspecPath, ".workflow-states", `${changeId}.json`);
5372
5808
  try {
5373
- const content = await fs5__namespace.readFile(statePath, "utf-8");
5809
+ const content = await fs4__namespace.readFile(statePath, "utf-8");
5374
5810
  this.state = JSON.parse(content, (key, value) => {
5375
5811
  if (key.endsWith("At") && typeof value === "string") {
5376
5812
  return new Date(value);
@@ -5383,7 +5819,7 @@ var WorkflowEngine = class {
5383
5819
  const changesDir = path5__namespace.join(this.openspecPath, "changes");
5384
5820
  const changeFile = path5__namespace.join(changesDir, `${changeId}.md`);
5385
5821
  try {
5386
- const content = await fs5__namespace.readFile(changeFile, "utf-8");
5822
+ const content = await fs4__namespace.readFile(changeFile, "utf-8");
5387
5823
  const parsed = this.parseChangeRecord(content);
5388
5824
  if (parsed && parsed.status === "running") {
5389
5825
  this.state = parsed;
@@ -5448,8 +5884,8 @@ var WorkflowEngine = class {
5448
5884
  const archiveDir = path5__namespace.join(changesDir, "archive");
5449
5885
  const changeFile = path5__namespace.join(changesDir, `${changeId}.md`);
5450
5886
  const archiveFile = path5__namespace.join(archiveDir, `${changeId}.md`);
5451
- await fs5__namespace.mkdir(archiveDir, { recursive: true });
5452
- await fs5__namespace.rename(changeFile, archiveFile).catch(() => {
5887
+ await fs4__namespace.mkdir(archiveDir, { recursive: true });
5888
+ await fs4__namespace.rename(changeFile, archiveFile).catch(() => {
5453
5889
  });
5454
5890
  this.state = null;
5455
5891
  this.snapshots.clear();
@@ -5475,15 +5911,15 @@ var WorkflowEngine = class {
5475
5911
  const archiveDir = path5__namespace.join(changesDir, "archive");
5476
5912
  const specDir = path5__namespace.join(this.openspecPath, "spec");
5477
5913
  const statesDir = path5__namespace.join(this.openspecPath, ".workflow-states");
5478
- await fs5__namespace.mkdir(archiveDir, { recursive: true });
5479
- await fs5__namespace.mkdir(specDir, { recursive: true });
5480
- await fs5__namespace.mkdir(statesDir, { recursive: true });
5914
+ await fs4__namespace.mkdir(archiveDir, { recursive: true });
5915
+ await fs4__namespace.mkdir(specDir, { recursive: true });
5916
+ await fs4__namespace.mkdir(statesDir, { recursive: true });
5481
5917
  }
5482
5918
  async restoreState() {
5483
5919
  const activePath = path5__namespace.join(this.openspecPath, ".workflow-active.json");
5484
5920
  let activeId = null;
5485
5921
  try {
5486
- const activeContent = await fs5__namespace.readFile(activePath, "utf-8");
5922
+ const activeContent = await fs4__namespace.readFile(activePath, "utf-8");
5487
5923
  const activeData = JSON.parse(activeContent);
5488
5924
  activeId = activeData.activeId;
5489
5925
  } catch {
@@ -5491,7 +5927,7 @@ var WorkflowEngine = class {
5491
5927
  if (activeId) {
5492
5928
  const statePath = path5__namespace.join(this.openspecPath, ".workflow-states", `${activeId}.json`);
5493
5929
  try {
5494
- const content = await fs5__namespace.readFile(statePath, "utf-8");
5930
+ const content = await fs4__namespace.readFile(statePath, "utf-8");
5495
5931
  this.state = JSON.parse(content, (key, value) => {
5496
5932
  if (key.endsWith("At") && typeof value === "string") {
5497
5933
  return new Date(value);
@@ -5504,7 +5940,7 @@ var WorkflowEngine = class {
5504
5940
  }
5505
5941
  const oldStatePath = path5__namespace.join(this.openspecPath, ".workflow-state.json");
5506
5942
  try {
5507
- const content = await fs5__namespace.readFile(oldStatePath, "utf-8");
5943
+ const content = await fs4__namespace.readFile(oldStatePath, "utf-8");
5508
5944
  this.state = JSON.parse(content, (key, value) => {
5509
5945
  if (key.endsWith("At") && typeof value === "string") {
5510
5946
  return new Date(value);
@@ -5513,7 +5949,7 @@ var WorkflowEngine = class {
5513
5949
  });
5514
5950
  if (this.state) {
5515
5951
  await this.saveState();
5516
- await fs5__namespace.unlink(oldStatePath).catch(() => {
5952
+ await fs4__namespace.unlink(oldStatePath).catch(() => {
5517
5953
  });
5518
5954
  }
5519
5955
  } catch (e) {
@@ -5527,16 +5963,16 @@ var WorkflowEngine = class {
5527
5963
  async saveState() {
5528
5964
  if (!this.state) return;
5529
5965
  const statesDir = path5__namespace.join(this.openspecPath, ".workflow-states");
5530
- await fs5__namespace.mkdir(statesDir, { recursive: true });
5966
+ await fs4__namespace.mkdir(statesDir, { recursive: true });
5531
5967
  const statePath = path5__namespace.join(statesDir, `${this.state.id}.json`);
5532
- await fs5__namespace.writeFile(statePath, JSON.stringify(this.state, null, 2));
5968
+ await fs4__namespace.writeFile(statePath, JSON.stringify(this.state, null, 2));
5533
5969
  const activePath = path5__namespace.join(this.openspecPath, ".workflow-active.json");
5534
- await fs5__namespace.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
5970
+ await fs4__namespace.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
5535
5971
  }
5536
5972
  async restoreSnapshots() {
5537
5973
  const snapshotsPath = path5__namespace.join(this.openspecPath, ".workflow-snapshots.json");
5538
5974
  try {
5539
- const content = await fs5__namespace.readFile(snapshotsPath, "utf-8");
5975
+ const content = await fs4__namespace.readFile(snapshotsPath, "utf-8");
5540
5976
  const data = JSON.parse(content, (key, value) => {
5541
5977
  if (key === "timestamp" && typeof value === "string") {
5542
5978
  return new Date(value);
@@ -5553,7 +5989,7 @@ var WorkflowEngine = class {
5553
5989
  async saveSnapshots() {
5554
5990
  const snapshotsPath = path5__namespace.join(this.openspecPath, ".workflow-snapshots.json");
5555
5991
  const data = Array.from(this.snapshots.values());
5556
- await fs5__namespace.writeFile(snapshotsPath, JSON.stringify(data, null, 2));
5992
+ await fs4__namespace.writeFile(snapshotsPath, JSON.stringify(data, null, 2));
5557
5993
  }
5558
5994
  async createSnapshot() {
5559
5995
  if (!this.state) return;
@@ -5574,7 +6010,7 @@ var WorkflowEngine = class {
5574
6010
  async createChangeRecord() {
5575
6011
  if (!this.state) return;
5576
6012
  const changePath = path5__namespace.join(this.openspecPath, "changes", `${this.state.id}.md`);
5577
- await fs5__namespace.writeFile(changePath, this.formatChangeRecord());
6013
+ await fs4__namespace.writeFile(changePath, this.formatChangeRecord());
5578
6014
  }
5579
6015
  async updateChangeRecord(status) {
5580
6016
  if (!this.state) return;
@@ -5582,7 +6018,7 @@ var WorkflowEngine = class {
5582
6018
  this.state.status = status;
5583
6019
  }
5584
6020
  const changePath = path5__namespace.join(this.openspecPath, "changes", `${this.state.id}.md`);
5585
- await fs5__namespace.writeFile(changePath, this.formatChangeRecord());
6021
+ await fs4__namespace.writeFile(changePath, this.formatChangeRecord());
5586
6022
  }
5587
6023
  formatChangeRecord() {
5588
6024
  if (!this.state) return "";
@@ -5646,7 +6082,7 @@ ${this.state.steps.map((s) => `- [${s.status === "completed" ? "x" : " "}] ${s.s
5646
6082
 
5647
6083
  ${this.state.artifacts.map((a) => `- ${a}`).join("\n") || "\u6682\u65E0"}
5648
6084
  `;
5649
- await fs5__namespace.writeFile(specPath, content);
6085
+ await fs4__namespace.writeFile(specPath, content);
5650
6086
  }
5651
6087
  };
5652
6088
  var ConfirmationRequiredError = class extends Error {
@@ -6143,15 +6579,15 @@ async function loadProjectContext(workingDirectory) {
6143
6579
  devStandards: ""
6144
6580
  };
6145
6581
  try {
6146
- context.agentsMd = await fs5__namespace.readFile(path5__namespace.join(workingDirectory, "AGENTS.md"), "utf-8");
6582
+ context.agentsMd = await fs4__namespace.readFile(path5__namespace.join(workingDirectory, "AGENTS.md"), "utf-8");
6147
6583
  } catch {
6148
6584
  }
6149
6585
  try {
6150
- context.configYaml = await fs5__namespace.readFile(path5__namespace.join(workingDirectory, "openspec", "config.yaml"), "utf-8");
6586
+ context.configYaml = await fs4__namespace.readFile(path5__namespace.join(workingDirectory, "openspec", "config.yaml"), "utf-8");
6151
6587
  } catch {
6152
6588
  }
6153
6589
  try {
6154
- context.devStandards = await fs5__namespace.readFile(path5__namespace.join(workingDirectory, ".sf-cli", "norms", "devstanded.md"), "utf-8");
6590
+ context.devStandards = await fs4__namespace.readFile(path5__namespace.join(workingDirectory, ".sf-cli", "norms", "devstanded.md"), "utf-8");
6155
6591
  } catch {
6156
6592
  }
6157
6593
  return context;
@@ -7073,9 +7509,9 @@ async function handleFileReference(filePath, ctx) {
7073
7509
  const cwd = ctx.options.workingDirectory;
7074
7510
  const absolutePath = path5__namespace.isAbsolute(filePath) ? filePath : path5__namespace.join(cwd, filePath);
7075
7511
  try {
7076
- const stats = await fs5__namespace.stat(absolutePath);
7512
+ const stats = await fs4__namespace.stat(absolutePath);
7077
7513
  if (stats.isDirectory()) {
7078
- const files = await fs5__namespace.readdir(absolutePath);
7514
+ const files = await fs4__namespace.readdir(absolutePath);
7079
7515
  return {
7080
7516
  output: chalk9__default.default.cyan(`\u{1F4C1} ${filePath}/`) + chalk9__default.default.gray(`
7081
7517
  ${files.slice(0, 20).join("\n")}`) + (files.length > 20 ? chalk9__default.default.gray(`
@@ -7083,7 +7519,7 @@ ${files.slice(0, 20).join("\n")}`) + (files.length > 20 ? chalk9__default.defaul
7083
7519
  contextUsed: 0
7084
7520
  };
7085
7521
  }
7086
- const content = await fs5__namespace.readFile(absolutePath, "utf-8");
7522
+ const content = await fs4__namespace.readFile(absolutePath, "utf-8");
7087
7523
  const lines = content.split("\n");
7088
7524
  ctx.contextManager.addMessage({
7089
7525
  role: "user",
@@ -7161,6 +7597,36 @@ async function executeShell(command, ctx) {
7161
7597
  // src/commands/natural.ts
7162
7598
  init_cjs_shims();
7163
7599
  init_new();
7600
+ var LoadingIndicator3 = class {
7601
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7602
+ frameIndex = 0;
7603
+ interval = null;
7604
+ message;
7605
+ constructor(message) {
7606
+ this.message = message;
7607
+ }
7608
+ start() {
7609
+ process.stdout.write("\x1B[?25l");
7610
+ this.interval = setInterval(() => {
7611
+ const frame = this.frames[this.frameIndex];
7612
+ process.stdout.write(`\r${chalk9__default.default.cyan(frame)} ${this.message}...`);
7613
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
7614
+ }, 80);
7615
+ }
7616
+ stop(finalMessage) {
7617
+ if (this.interval) {
7618
+ clearInterval(this.interval);
7619
+ this.interval = null;
7620
+ }
7621
+ process.stdout.write("\x1B[?25h");
7622
+ if (finalMessage) {
7623
+ process.stdout.write(`\r${finalMessage}
7624
+ `);
7625
+ } else {
7626
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
7627
+ }
7628
+ }
7629
+ };
7164
7630
  async function handleNaturalLanguage(input, ctx) {
7165
7631
  const trimmedInput = input.trim();
7166
7632
  const session = getActiveSession();
@@ -7180,6 +7646,8 @@ async function handleNaturalLanguage(input, ctx) {
7180
7646
  contextUsed: 0
7181
7647
  };
7182
7648
  }
7649
+ const loader = new LoadingIndicator3("AI \u601D\u8003\u4E2D");
7650
+ loader.start();
7183
7651
  try {
7184
7652
  const response = await ctx.modelService.sendMessage(
7185
7653
  [
@@ -7197,6 +7665,7 @@ async function handleNaturalLanguage(input, ctx) {
7197
7665
  maxTokens: 2e3
7198
7666
  }
7199
7667
  );
7668
+ loader.stop();
7200
7669
  ctx.contextManager.addMessage({
7201
7670
  role: "user",
7202
7671
  content: trimmedInput
@@ -7210,6 +7679,7 @@ async function handleNaturalLanguage(input, ctx) {
7210
7679
  contextUsed: response.usage?.totalTokens || 0
7211
7680
  };
7212
7681
  } catch (error) {
7682
+ loader.stop();
7213
7683
  const errorMessage = error.message;
7214
7684
  if (errorMessage.includes("\u672A\u914D\u7F6E") || errorMessage.includes("\u672A\u521D\u59CB\u5316")) {
7215
7685
  return {
@@ -7635,14 +8105,14 @@ ${summary}`,
7635
8105
  async persist() {
7636
8106
  if (!this.persistPath) return;
7637
8107
  const dir = path5__namespace.dirname(this.persistPath);
7638
- await fs5__namespace.mkdir(dir, { recursive: true });
8108
+ await fs4__namespace.mkdir(dir, { recursive: true });
7639
8109
  const data = {
7640
8110
  messages: this.state.messages,
7641
8111
  totalTokens: this.state.totalTokens,
7642
8112
  limit: this.state.limit,
7643
8113
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
7644
8114
  };
7645
- await fs5__namespace.writeFile(this.persistPath, JSON.stringify(data, null, 2), "utf-8");
8115
+ await fs4__namespace.writeFile(this.persistPath, JSON.stringify(data, null, 2), "utf-8");
7646
8116
  }
7647
8117
  /**
7648
8118
  * 加载持久化的上下文
@@ -7650,7 +8120,7 @@ ${summary}`,
7650
8120
  async loadPersistedContext() {
7651
8121
  if (!this.persistPath) return;
7652
8122
  try {
7653
- const content = await fs5__namespace.readFile(this.persistPath, "utf-8");
8123
+ const content = await fs4__namespace.readFile(this.persistPath, "utf-8");
7654
8124
  const data = JSON.parse(content);
7655
8125
  this.state.messages = data.messages || [];
7656
8126
  this.state.totalTokens = data.totalTokens || 0;
@@ -7739,7 +8209,7 @@ var ConfigManager = class {
7739
8209
  this.projectPath = projectPath;
7740
8210
  this.configPath = path5__namespace.join(projectPath, ".sf-cli", "config.json");
7741
8211
  try {
7742
- const content = await fs5__namespace.readFile(this.configPath, "utf-8");
8212
+ const content = await fs4__namespace.readFile(this.configPath, "utf-8");
7743
8213
  const loaded = JSON.parse(content);
7744
8214
  if (loaded.apiKey && loaded.apiKeyEncrypted) {
7745
8215
  loaded.apiKey = this.decrypt(loaded.apiKey);
@@ -7758,8 +8228,8 @@ var ConfigManager = class {
7758
8228
  configToSave.apiKey = encrypted;
7759
8229
  configToSave.apiKeyEncrypted = true;
7760
8230
  }
7761
- await fs5__namespace.mkdir(path5__namespace.dirname(this.configPath), { recursive: true });
7762
- await fs5__namespace.writeFile(
8231
+ await fs4__namespace.mkdir(path5__namespace.dirname(this.configPath), { recursive: true });
8232
+ await fs4__namespace.writeFile(
7763
8233
  this.configPath,
7764
8234
  JSON.stringify(configToSave, null, 2),
7765
8235
  "utf-8"
@@ -8011,8 +8481,8 @@ var ModelService = class {
8011
8481
  const dailyPath = path5__namespace.join(this.statsPath, "daily.json");
8012
8482
  const totalPath = path5__namespace.join(this.statsPath, "total.json");
8013
8483
  const [daily, total] = await Promise.all([
8014
- fs5__namespace.readFile(dailyPath, "utf-8").catch(() => "{}"),
8015
- fs5__namespace.readFile(totalPath, "utf-8").catch(() => '{"totalInput":0,"totalOutput":0}')
8484
+ fs4__namespace.readFile(dailyPath, "utf-8").catch(() => "{}"),
8485
+ fs4__namespace.readFile(totalPath, "utf-8").catch(() => '{"totalInput":0,"totalOutput":0}')
8016
8486
  ]);
8017
8487
  const dailyData = JSON.parse(daily);
8018
8488
  const totalData = JSON.parse(total);
@@ -8025,12 +8495,12 @@ var ModelService = class {
8025
8495
  async saveStats() {
8026
8496
  if (!this.statsPath) return;
8027
8497
  try {
8028
- await fs5__namespace.mkdir(this.statsPath, { recursive: true });
8498
+ await fs4__namespace.mkdir(this.statsPath, { recursive: true });
8029
8499
  const dailyPath = path5__namespace.join(this.statsPath, "daily.json");
8030
8500
  const totalPath = path5__namespace.join(this.statsPath, "total.json");
8031
8501
  await Promise.all([
8032
- fs5__namespace.writeFile(dailyPath, JSON.stringify(this.stats.byDate, null, 2)),
8033
- fs5__namespace.writeFile(totalPath, JSON.stringify({
8502
+ fs4__namespace.writeFile(dailyPath, JSON.stringify(this.stats.byDate, null, 2)),
8503
+ fs4__namespace.writeFile(totalPath, JSON.stringify({
8034
8504
  totalInput: this.stats.totalInput,
8035
8505
  totalOutput: this.stats.totalOutput
8036
8506
  }))
@@ -8243,7 +8713,7 @@ var Completer = class {
8243
8713
  prefix = filePath.slice(lastSlash + 1);
8244
8714
  }
8245
8715
  try {
8246
- const entries = await fs5__namespace.readdir(dirPath, { withFileTypes: true });
8716
+ const entries = await fs4__namespace.readdir(dirPath, { withFileTypes: true });
8247
8717
  const matches = [];
8248
8718
  for (const entry of entries) {
8249
8719
  if (entry.name.startsWith(prefix)) {
@@ -8325,7 +8795,7 @@ async function startInteractiveMode(options) {
8325
8795
  const historyFile = path5__namespace.join(options.workingDirectory, ".sf-cli", "history.json");
8326
8796
  let history = [];
8327
8797
  try {
8328
- const historyData = await fs5__namespace.readFile(historyFile, "utf-8");
8798
+ const historyData = await fs4__namespace.readFile(historyFile, "utf-8");
8329
8799
  history = JSON.parse(historyData);
8330
8800
  } catch {
8331
8801
  }
@@ -8412,8 +8882,8 @@ async function startInteractiveMode(options) {
8412
8882
  }
8413
8883
  const saveHistory = async () => {
8414
8884
  try {
8415
- await fs5__namespace.mkdir(path5__namespace.dirname(historyFile), { recursive: true });
8416
- await fs5__namespace.writeFile(historyFile, JSON.stringify(history.slice(-500)));
8885
+ await fs4__namespace.mkdir(path5__namespace.dirname(historyFile), { recursive: true });
8886
+ await fs4__namespace.writeFile(historyFile, JSON.stringify(history.slice(-500)));
8417
8887
  } catch {
8418
8888
  }
8419
8889
  };