@nick848/sf-cli 1.0.17 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -180,7 +180,7 @@ async function executeWorkflow(ctx) {
180
180
  for (const url of urls) {
181
181
  lines.push(chalk9__default.default.gray(` \u{1F4CE} ${url}`));
182
182
  try {
183
- const resource = await fetchAndAnalyzeReference(url, ctx);
183
+ const resource = await fetchAndAnalyzeReference(url, ctx, activeSession.context || void 0);
184
184
  activeSession.referenceResources.push(resource);
185
185
  lines.push(chalk9__default.default.green(` \u2713 \u5DF2\u5206\u6790`));
186
186
  activeSession.refinedRequirement += `
@@ -219,12 +219,26 @@ ${resource.analysis}`;
219
219
  lines.push("");
220
220
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 5/9: BDD \u573A\u666F\u62C6\u89E3 \u2501\u2501\u2501"));
221
221
  lines.push("");
222
- activeSession.bddScenarios = generateBDDScenarios(
223
- activeSession.refinedRequirement,
224
- activeSession.context,
225
- activeSession.clarificationQuestions,
226
- activeSession.referenceResources
227
- );
222
+ const loader = new LoadingIndicator("AI \u6B63\u5728\u751F\u6210 BDD \u573A\u666F");
223
+ loader.start();
224
+ try {
225
+ activeSession.bddScenarios = await generateBDDScenariosWithAI(
226
+ activeSession.refinedRequirement,
227
+ activeSession.context,
228
+ activeSession.clarificationQuestions,
229
+ activeSession.referenceResources,
230
+ ctx
231
+ );
232
+ loader.stop(chalk9__default.default.green(" \u2713 BDD \u573A\u666F\u5DF2\u751F\u6210"));
233
+ } catch {
234
+ loader.stop(chalk9__default.default.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840 BDD \u751F\u6210"));
235
+ activeSession.bddScenarios = generateBDDScenarios(
236
+ activeSession.refinedRequirement,
237
+ activeSession.context,
238
+ activeSession.clarificationQuestions,
239
+ activeSession.referenceResources
240
+ );
241
+ }
228
242
  for (const scenario of activeSession.bddScenarios) {
229
243
  lines.push(chalk9__default.default.white(` Feature: ${scenario.feature}`));
230
244
  for (const s of scenario.scenarios.slice(0, 3)) {
@@ -271,7 +285,7 @@ ${resource.analysis}`;
271
285
  lines.push("");
272
286
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 7/9: TDD \u6D4B\u8BD5\u751F\u6210 \u2501\u2501\u2501"));
273
287
  lines.push("");
274
- activeSession.testFiles = await generateTests(ctx.options.workingDirectory, activeSession);
288
+ activeSession.testFiles = await generateTests(ctx.options.workingDirectory, activeSession, ctx);
275
289
  lines.push(chalk9__default.default.green(" \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210"));
276
290
  for (const file of activeSession.testFiles) {
277
291
  lines.push(chalk9__default.default.gray(` - ${file}`));
@@ -568,6 +582,8 @@ function getCategoryLabel(category) {
568
582
  async function executeDevelopment(ctx, session) {
569
583
  const workingDir = ctx.options.workingDirectory;
570
584
  const files = [];
585
+ const loader = new LoadingIndicator("AI \u6B63\u5728\u751F\u6210\u4EE3\u7801");
586
+ loader.start();
571
587
  try {
572
588
  const systemPrompt = buildDevelopmentPrompt(session);
573
589
  const messages = [
@@ -575,7 +591,12 @@ async function executeDevelopment(ctx, session) {
575
591
  role: "system",
576
592
  content: `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u524D\u7AEF\u5F00\u53D1\u5DE5\u7A0B\u5E08\u3002\u8BF7\u6839\u636E\u9700\u6C42\u89C4\u683C\u751F\u6210\u4EE3\u7801\u5B9E\u73B0\u3002
577
593
 
578
- \u8981\u6C42\uFF1A
594
+ \u26A0\uFE0F \u91CD\u8981\u89C4\u5219\uFF1A
595
+ 1. \u6280\u672F\u5B9E\u73B0\u5FC5\u987B\u4E25\u683C\u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u5F00\u53D1\u89C4\u8303
596
+ 2. \u53C2\u8003\u8D44\u6E90\uFF08\u5982\u679C\u6709\uFF09\u4EC5\u7528\u4E8E\u7406\u89E3\u4E1A\u52A1\u529F\u80FD\uFF0C\u4E0D\u8981\u590D\u5236\u5176\u6280\u672F\u5B9E\u73B0
597
+ 3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808\u548C\u6846\u67B6
598
+
599
+ \u4EE3\u7801\u8981\u6C42\uFF1A
579
600
  1. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
580
601
  2. \u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u4EE3\u7801\u98CE\u683C\u548C\u89C4\u8303
581
602
  3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808
@@ -587,19 +608,23 @@ async function executeDevelopment(ctx, session) {
587
608
  - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
588
609
  - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
589
610
 
590
- ${session.context?.devStandards ? `\u5F00\u53D1\u89C4\u8303\uFF1A
591
- ${session.context.devStandards.slice(0, 2e3)}` : ""}`
611
+ ${session.context?.devStandards ? `\u3010\u5FC5\u987B\u9075\u5FAA\u7684\u5F00\u53D1\u89C4\u8303\u3011
612
+ ${session.context.devStandards.slice(0, 2500)}` : ""}`
592
613
  },
593
614
  {
594
615
  role: "user",
595
616
  content: systemPrompt
596
617
  }
597
618
  ];
619
+ loader.update("\u6B63\u5728\u8C03\u7528 AI \u6A21\u578B");
598
620
  const response = await ctx.modelService.sendMessage(messages, {
599
621
  temperature: 0.3,
600
622
  maxTokens: 8e3,
601
- agent: "frontend-dev"
623
+ agent: "frontend-dev",
624
+ timeout: 18e4
625
+ // 3 分钟超时
602
626
  });
627
+ loader.update("\u6B63\u5728\u89E3\u6790\u4EE3\u7801");
603
628
  const codeBlocks = parseCodeBlocks(response.content);
604
629
  for (const block of codeBlocks) {
605
630
  const filePath = path4__namespace.join(workingDir, block.filename);
@@ -628,8 +653,10 @@ export function ${featureName.replace(/[^a-zA-Z0-9]/g, "")}() {
628
653
  await fs4__namespace.writeFile(filePath, stubCode, "utf-8");
629
654
  files.push(`src/features/${fileName}`);
630
655
  }
656
+ loader.stop(chalk9__default.default.green(` \u2713 \u5DF2\u751F\u6210 ${files.length} \u4E2A\u6587\u4EF6`));
631
657
  return { success: true, files };
632
658
  } catch (error) {
659
+ loader.stop();
633
660
  return {
634
661
  success: false,
635
662
  files: [],
@@ -913,36 +940,130 @@ function generateBDDScenarios(requirement, context, questions, references = [])
913
940
  }
914
941
  return scenarios;
915
942
  }
943
+ async function generateBDDScenariosWithAI(requirement, context, questions, references, ctx) {
944
+ const prompt2 = `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u6D4B\u8BD5\u5DE5\u7A0B\u5E08\u548C\u4E1A\u52A1\u5206\u6790\u5E08\u3002\u8BF7\u6839\u636E\u4EE5\u4E0B\u9700\u6C42\u751F\u6210\u8BE6\u7EC6\u7684 BDD (Behavior Driven Development) \u573A\u666F\u3002
945
+
946
+ ## \u9700\u6C42\u63CF\u8FF0
947
+ ${requirement}
948
+
949
+ ## \u9879\u76EE\u4E0A\u4E0B\u6587
950
+ - \u6280\u672F\u6808: ${context.techStack?.join(", ") || "\u672A\u6307\u5B9A"}
951
+ - \u6846\u67B6: ${context.framework || "\u672A\u6307\u5B9A"}
952
+ ${context.devStandards ? `
953
+ ## \u9879\u76EE\u5F00\u53D1\u89C4\u8303\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09
954
+ ${context.devStandards.slice(0, 2e3)}
955
+ ` : ""}
956
+
957
+ ## \u6F84\u6E05\u95EE\u7B54
958
+ ${questions.filter((q) => q.answered).map((q) => `- Q: ${q.question}
959
+ A: ${q.answer}`).join("\n")}
960
+
961
+ ## \u53C2\u8003\u8D44\u6E90\u5206\u6790\uFF08\u4EC5\u4F5C\u4E1A\u52A1\u53C2\u8003\uFF0C\u6280\u672F\u5B9E\u73B0\u9075\u5FAA\u9879\u76EE\u89C4\u8303\uFF09
962
+ ${references.length > 0 ? references.map((r) => `### ${r.url}
963
+ ${r.analysis}`).join("\n\n") : "\u65E0"}
964
+
965
+ ## \u8981\u6C42
966
+ 1. \u6BCF\u4E2A\u529F\u80FD\u6A21\u5757\u751F\u6210\u4E00\u4E2A\u72EC\u7ACB\u7684 Feature
967
+ 2. \u6BCF\u4E2A Feature \u5305\u542B\u591A\u4E2A\u5177\u4F53\u7684 Scenario
968
+ 3. \u4F7F\u7528 Given-When-Then \u683C\u5F0F
969
+ 4. \u573A\u666F\u8981\u8986\u76D6: \u6B63\u5E38\u6D41\u7A0B\u3001\u8FB9\u754C\u60C5\u51B5\u3001\u5F02\u5E38\u5904\u7406
970
+ 5. \u573A\u666F\u8981\u5177\u4F53\u53EF\u6D4B\u8BD5\uFF0C\u805A\u7126\u4E1A\u52A1\u903B\u8F91
971
+ 6. \u26A0\uFE0F \u53C2\u8003\u8D44\u6E90\u4EC5\u7528\u4E8E\u7406\u89E3\u4E1A\u52A1\u529F\u80FD\uFF0C\u6280\u672F\u5B9E\u73B0\u5FC5\u987B\u9075\u5FAA\u9879\u76EE\u89C4\u8303
972
+
973
+ ## \u8F93\u51FA\u683C\u5F0F (JSON)
974
+ \`\`\`json
975
+ [
976
+ {
977
+ "feature": "\u529F\u80FD\u540D\u79F0",
978
+ "description": "\u529F\u80FD\u63CF\u8FF0",
979
+ "scenarios": [
980
+ {
981
+ "name": "\u573A\u666F\u540D\u79F0",
982
+ "given": ["\u524D\u7F6E\u6761\u4EF61", "\u524D\u7F6E\u6761\u4EF62"],
983
+ "when": ["\u64CD\u4F5C1", "\u64CD\u4F5C2"],
984
+ "then": ["\u9884\u671F\u7ED3\u679C1", "\u9884\u671F\u7ED3\u679C2"]
985
+ }
986
+ ]
987
+ }
988
+ ]
989
+ \`\`\`
990
+
991
+ \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\uFF0C\u4E0D\u8981\u6709\u5176\u4ED6\u5185\u5BB9\u3002`;
992
+ const response = await ctx.modelService.sendMessage([
993
+ { role: "user", content: prompt2 }
994
+ ], {
995
+ temperature: 0.3,
996
+ maxTokens: 4e3,
997
+ timeout: 12e4
998
+ });
999
+ try {
1000
+ const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
1001
+ if (jsonMatch) {
1002
+ return JSON.parse(jsonMatch[1].trim());
1003
+ }
1004
+ return JSON.parse(response.content);
1005
+ } catch {
1006
+ return generateBDDScenarios(requirement, context, questions, references);
1007
+ }
1008
+ }
916
1009
  function extractFeaturesFromReference(ref) {
917
1010
  const features = [];
918
- const analysis = ref.analysis.toLowerCase();
919
- if (analysis.includes("\u8F93\u5165") || analysis.includes("\u8868\u5355")) {
920
- features.push({
921
- title: "\u8F93\u5165\u8868\u5355",
922
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u8F93\u5165\u8868\u5355\u529F\u80FD",
923
- hasInput: true
924
- });
925
- }
926
- if (analysis.includes("\u6309\u94AE") || analysis.includes("\u64CD\u4F5C")) {
927
- features.push({
928
- title: "\u4EA4\u4E92\u6309\u94AE",
929
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6309\u94AE\u4EA4\u4E92",
930
- hasInput: false
931
- });
1011
+ const analysis = ref.analysis;
1012
+ const featureSection = analysis.match(/###?\s*4\.\s*功能拆分建议[\s\S]*?(?=###?\s*\d|$)/i);
1013
+ if (featureSection) {
1014
+ const taskMatches = featureSection[0].matchAll(/[-*]\s*\*\*([^*]+)\*\*[::]?\s*([^\n]+)/g);
1015
+ for (const match of taskMatches) {
1016
+ features.push({
1017
+ title: match[1].trim(),
1018
+ description: match[2].trim(),
1019
+ hasInput: match[2].includes("\u8F93\u5165") || match[2].includes("\u8868\u5355") || match[2].includes("\u7528\u6237")
1020
+ });
1021
+ }
932
1022
  }
933
- if (analysis.includes("\u8868\u683C") || analysis.includes("\u5217\u8868")) {
934
- features.push({
935
- title: "\u6570\u636E\u5217\u8868",
936
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6570\u636E\u5C55\u793A",
937
- hasInput: false
938
- });
1023
+ const bizSection = analysis.match(/###?\s*1\.\s*业务功能分析[\s\S]*?(?=###?\s*\d|$)/i);
1024
+ if (bizSection && features.length === 0) {
1025
+ const lines = bizSection[0].split("\n").filter((l) => l.trim().startsWith("-") || l.trim().startsWith("*"));
1026
+ for (const line of lines.slice(0, 5)) {
1027
+ const content = line.replace(/^[-*]\s*/, "").trim();
1028
+ if (content.length > 5) {
1029
+ features.push({
1030
+ title: content.slice(0, 20),
1031
+ description: content,
1032
+ hasInput: content.includes("\u8F93\u5165") || content.includes("\u586B\u5199")
1033
+ });
1034
+ }
1035
+ }
939
1036
  }
940
- if (analysis.includes("\u56FE\u8868") || analysis.includes("\u53EF\u89C6\u5316")) {
941
- features.push({
942
- title: "\u56FE\u8868\u5C55\u793A",
943
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u56FE\u8868\u53EF\u89C6\u5316",
944
- hasInput: false
945
- });
1037
+ if (features.length === 0) {
1038
+ const lowerAnalysis = analysis.toLowerCase();
1039
+ if (lowerAnalysis.includes("\u8F93\u5165") || lowerAnalysis.includes("\u8868\u5355")) {
1040
+ features.push({
1041
+ title: "\u8F93\u5165\u8868\u5355",
1042
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u8F93\u5165\u8868\u5355\u529F\u80FD",
1043
+ hasInput: true
1044
+ });
1045
+ }
1046
+ if (lowerAnalysis.includes("\u6309\u94AE") || lowerAnalysis.includes("\u64CD\u4F5C")) {
1047
+ features.push({
1048
+ title: "\u4EA4\u4E92\u6309\u94AE",
1049
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6309\u94AE\u4EA4\u4E92",
1050
+ hasInput: false
1051
+ });
1052
+ }
1053
+ if (lowerAnalysis.includes("\u8868\u683C") || lowerAnalysis.includes("\u5217\u8868")) {
1054
+ features.push({
1055
+ title: "\u6570\u636E\u5217\u8868",
1056
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6570\u636E\u5C55\u793A",
1057
+ hasInput: false
1058
+ });
1059
+ }
1060
+ if (lowerAnalysis.includes("\u56FE\u8868") || lowerAnalysis.includes("\u53EF\u89C6\u5316")) {
1061
+ features.push({
1062
+ title: "\u56FE\u8868\u5C55\u793A",
1063
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u56FE\u8868\u53EF\u89C6\u5316",
1064
+ hasInput: false
1065
+ });
1066
+ }
946
1067
  }
947
1068
  if (features.length === 0) {
948
1069
  features.push({
@@ -1106,33 +1227,119 @@ function formatSpecFile(session) {
1106
1227
  lines.push("**\u786E\u8BA4\u72B6\u6001**: \u23F3 \u7B49\u5F85\u786E\u8BA4");
1107
1228
  return lines.join("\n");
1108
1229
  }
1109
- async function generateTests(workingDir, session) {
1230
+ async function generateTests(workingDir, session, ctx) {
1110
1231
  const testDir = path4__namespace.join(workingDir, "tests");
1111
1232
  await fs4__namespace.mkdir(testDir, { recursive: true });
1112
1233
  const testFiles = [];
1113
- for (const scenario of session.bddScenarios) {
1114
- const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
1115
- const testPath = path4__namespace.join(testDir, `${testName}.test.ts`);
1116
- const content = generateTestFile(scenario);
1117
- await fs4__namespace.writeFile(testPath, content, "utf-8");
1118
- testFiles.push(`tests/${testName}.test.ts`);
1234
+ if (ctx?.modelService) {
1235
+ for (const scenario of session.bddScenarios) {
1236
+ const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
1237
+ const testPath = path4__namespace.join(testDir, `${testName}.test.ts`);
1238
+ const loader = new LoadingIndicator(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
1239
+ loader.start();
1240
+ try {
1241
+ const content = await generateTestFileWithAI(scenario, session, ctx);
1242
+ await fs4__namespace.writeFile(testPath, content, "utf-8");
1243
+ testFiles.push(`tests/${testName}.test.ts`);
1244
+ loader.stop(chalk9__default.default.green(` \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210`));
1245
+ } catch {
1246
+ const content = generateTestFile(scenario, session);
1247
+ await fs4__namespace.writeFile(testPath, content, "utf-8");
1248
+ testFiles.push(`tests/${testName}.test.ts`);
1249
+ loader.stop(chalk9__default.default.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840\u6D4B\u8BD5\u6A21\u677F"));
1250
+ }
1251
+ }
1252
+ } else {
1253
+ for (const scenario of session.bddScenarios) {
1254
+ const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
1255
+ const testPath = path4__namespace.join(testDir, `${testName}.test.ts`);
1256
+ const content = generateTestFile(scenario, session);
1257
+ await fs4__namespace.writeFile(testPath, content, "utf-8");
1258
+ testFiles.push(`tests/${testName}.test.ts`);
1259
+ }
1119
1260
  }
1120
1261
  return testFiles;
1121
1262
  }
1122
- function generateTestFile(scenario) {
1263
+ async function generateTestFileWithAI(scenario, session, ctx) {
1264
+ const prompt2 = `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u6D4B\u8BD5\u5DE5\u7A0B\u5E08\u3002\u8BF7\u6839\u636E\u4EE5\u4E0B BDD \u573A\u666F\u751F\u6210\u5B8C\u6574\u7684 Vitest \u6D4B\u8BD5\u4EE3\u7801\u3002
1265
+
1266
+ ## \u529F\u80FD\u540D\u79F0
1267
+ ${scenario.feature}
1268
+
1269
+ ## BDD \u573A\u666F
1270
+ ${scenario.scenarios.map((s) => `
1271
+ ### ${s.name}
1272
+ - Given: ${s.given.join(", ")}
1273
+ - When: ${s.when.join(", ")}
1274
+ - Then: ${s.then.join(", ")}
1275
+ `).join("\n")}
1276
+
1277
+ ## \u9879\u76EE\u4E0A\u4E0B\u6587
1278
+ - \u6280\u672F\u6808: ${session.context?.techStack?.join(", ") || "TypeScript"}
1279
+ - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
1280
+
1281
+ ## \u8981\u6C42
1282
+ 1. \u4F7F\u7528 vitest \u6D4B\u8BD5\u6846\u67B6 (describe, it, expect, beforeEach \u7B49)
1283
+ 2. \u6BCF\u4E2A\u573A\u666F\u751F\u6210\u4E00\u4E2A\u72EC\u7ACB\u7684\u6D4B\u8BD5\u7528\u4F8B
1284
+ 3. \u6D4B\u8BD5\u4EE3\u7801\u8981\u5B8C\u6574\u53EF\u8FD0\u884C\uFF0C\u5305\u542B\u5FC5\u8981\u7684 mock \u548C setup
1285
+ 4. \u4F7F\u7528\u4E2D\u6587\u6CE8\u91CA\u8BF4\u660E\u6D4B\u8BD5\u610F\u56FE
1286
+ 5. \u6D4B\u8BD5\u8981\u8986\u76D6\u6B63\u5E38\u6D41\u7A0B\u548C\u8FB9\u754C\u60C5\u51B5
1287
+
1288
+ \u8BF7\u76F4\u63A5\u8F93\u51FA\u6D4B\u8BD5\u4EE3\u7801\uFF0C\u4E0D\u9700\u8981\u89E3\u91CA\u3002`;
1289
+ const response = await ctx.modelService.sendMessage([
1290
+ { role: "user", content: prompt2 }
1291
+ ], {
1292
+ temperature: 0.3,
1293
+ maxTokens: 4e3
1294
+ });
1295
+ const codeMatch = response.content.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
1296
+ if (codeMatch) {
1297
+ return codeMatch[1].trim();
1298
+ }
1299
+ return response.content;
1300
+ }
1301
+ function generateTestFile(scenario, session) {
1123
1302
  const lines = [];
1124
- lines.push(`import { describe, it, expect } from 'vitest';`);
1303
+ lines.push(`import { describe, it, expect, beforeEach } from 'vitest';`);
1125
1304
  lines.push("");
1305
+ lines.push(`/**`);
1306
+ lines.push(` * ${scenario.feature} \u529F\u80FD\u6D4B\u8BD5`);
1307
+ lines.push(` * `);
1308
+ lines.push(` * BDD \u573A\u666F\u6570\u91CF: ${scenario.scenarios.length}`);
1309
+ if (session?.context?.techStack) {
1310
+ lines.push(` * \u6280\u672F\u6808: ${session.context.techStack.join(", ")}`);
1311
+ }
1312
+ lines.push(` */`);
1126
1313
  lines.push(`describe('${scenario.feature}', () => {`);
1127
1314
  for (const s of scenario.scenarios) {
1128
- lines.push(` it('${s.name}', () => {`);
1129
- lines.push(` // Given: ${s.given.join(", ")}`);
1130
- lines.push(` // When: ${s.when.join(", ")}`);
1131
- lines.push(` // Then: ${s.then.join(", ")}`);
1132
- lines.push(` expect(true).toBe(true); // TODO: \u5B9E\u73B0\u6D4B\u8BD5`);
1133
- lines.push(` });`);
1134
1315
  lines.push("");
1316
+ lines.push(` /**`);
1317
+ lines.push(` * \u573A\u666F: ${s.name}`);
1318
+ lines.push(` * Given: ${s.given.join(", ")}`);
1319
+ lines.push(` * When: ${s.when.join(", ")}`);
1320
+ lines.push(` * Then: ${s.then.join(", ")}`);
1321
+ lines.push(` */`);
1322
+ lines.push(` it('${s.name}', async () => {`);
1323
+ lines.push(` // Arrange (Given)`);
1324
+ for (const g of s.given) {
1325
+ lines.push(` // ${g}`);
1326
+ }
1327
+ lines.push(` const input = {}; // TODO: \u8BBE\u7F6E\u521D\u59CB\u72B6\u6001`);
1328
+ lines.push("");
1329
+ lines.push(` // Act (When)`);
1330
+ for (const w of s.when) {
1331
+ lines.push(` // ${w}`);
1332
+ }
1333
+ lines.push(` const result = {}; // TODO: \u6267\u884C\u64CD\u4F5C`);
1334
+ lines.push("");
1335
+ lines.push(` // Assert (Then)`);
1336
+ for (const t of s.then) {
1337
+ lines.push(` // ${t}`);
1338
+ }
1339
+ lines.push(` expect(result).toBeDefined(); // TODO: \u5B8C\u5584\u65AD\u8A00`);
1340
+ lines.push(` });`);
1135
1341
  }
1342
+ lines.push("");
1136
1343
  lines.push(`});`);
1137
1344
  return lines.join("\n");
1138
1345
  }
@@ -1178,7 +1385,7 @@ function extractUrls(text) {
1178
1385
  const matches = text.match(urlRegex);
1179
1386
  return matches ? [...new Set(matches)] : [];
1180
1387
  }
1181
- async function fetchAndAnalyzeReference(url, ctx) {
1388
+ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
1182
1389
  const type = detectResourceType(url);
1183
1390
  let content = "";
1184
1391
  let analysis = "";
@@ -1193,7 +1400,7 @@ async function fetchAndAnalyzeReference(url, ctx) {
1193
1400
  }
1194
1401
  content = await response.text();
1195
1402
  if (ctx.modelService.getCurrentModel()) {
1196
- analysis = await analyzeReferenceContent(url, content, type, ctx);
1403
+ analysis = await analyzeReferenceContent(url, content, type, ctx, projectContext);
1197
1404
  } else {
1198
1405
  analysis = extractBasicInfo(content, type);
1199
1406
  }
@@ -1214,40 +1421,72 @@ function detectResourceType(url) {
1214
1421
  }
1215
1422
  return "webpage";
1216
1423
  }
1217
- async function analyzeReferenceContent(url, content, type, ctx) {
1218
- const typePrompts = {
1219
- webpage: "\u5206\u6790\u8FD9\u4E2A\u7F51\u9875\u7684\u529F\u80FD\u3001UI\u7EC4\u4EF6\u548C\u4EA4\u4E92\u65B9\u5F0F",
1220
- design: "\u5206\u6790\u8FD9\u4E2A\u8BBE\u8BA1\u7A3F\u7684\u5E03\u5C40\u3001\u7EC4\u4EF6\u548C\u6837\u5F0F",
1221
- image: "\u63CF\u8FF0\u8FD9\u4E2A\u56FE\u7247\u7684\u5185\u5BB9\u548C\u8BBE\u8BA1\u5143\u7D20",
1222
- api: "\u5206\u6790\u8FD9\u4E2AAPI\u7684\u7ED3\u6784\u548C\u53C2\u6570"
1223
- };
1424
+ async function analyzeReferenceContent(url, content, type, ctx, projectContext) {
1224
1425
  const prompt2 = `
1225
- \u8BF7\u5206\u6790\u4EE5\u4E0B\u53C2\u8003\u8D44\u6E90\u7684 URL\uFF0C\u63D0\u53D6\u5BF9\u5F00\u53D1\u6709\u7528\u7684\u4FE1\u606F\uFF1A
1426
+ \u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u4EA7\u54C1\u7ECF\u7406\u3002\u8BF7\u5206\u6790\u4EE5\u4E0B\u53C2\u8003\u8D44\u6E90\uFF0C\u63D0\u53D6\u4E1A\u52A1\u529F\u80FD\u548C\u754C\u9762\u7ED3\u6784\u3002
1427
+
1428
+ ## \u91CD\u8981\u8BF4\u660E
1429
+ \u26A0\uFE0F \u6B64\u5206\u6790\u4EC5\u7528\u4E8E\u7406\u89E3\u4E1A\u52A1\u529F\u80FD\uFF0C**\u6280\u672F\u5B9E\u73B0\u65B9\u6848\u5FC5\u987B\u9075\u5FAA\u5F53\u524D\u9879\u76EE\u7684\u5F00\u53D1\u89C4\u8303**\u3002
1430
+ \u53C2\u8003\u8D44\u6E90\u4E2D\u7684\u6280\u672F\u6808\u3001\u6846\u67B6\u3001\u4EE3\u7801\u98CE\u683C\u4EC5\u4F9B\u53C2\u8003\uFF0C\u5B9E\u9645\u5F00\u53D1\u5E94\u4F7F\u7528\u9879\u76EE\u73B0\u6709\u89C4\u8303\u3002
1431
+
1432
+ ${projectContext ? `## \u5F53\u524D\u9879\u76EE\u89C4\u8303
1433
+ - \u9879\u76EE\u540D\u79F0: ${projectContext.name}
1434
+ - \u6280\u672F\u6808: ${projectContext.techStack.join(", ") || "\u672A\u6307\u5B9A"}
1435
+ - \u6846\u67B6: ${projectContext.framework || "\u672A\u6307\u5B9A"}
1436
+ - \u5F00\u53D1\u89C4\u8303\u6458\u8981: ${projectContext.devStandards?.slice(0, 1500) || "\u672A\u6307\u5B9A"}
1437
+ ` : ""}
1438
+
1439
+ ## \u53C2\u8003\u8D44\u6E90\u4FE1\u606F
1440
+ - URL: ${url}
1441
+ - \u7C7B\u578B: ${type}
1442
+
1443
+ ## \u7F51\u9875\u5185\u5BB9
1444
+ \`\`\`html
1445
+ ${content.slice(0, 8e3)}
1446
+ \`\`\`
1447
+
1448
+ ## \u5206\u6790\u8981\u6C42
1449
+
1450
+ \u8BF7\u6309\u7167\u4EE5\u4E0B\u7ED3\u6784\u8FDB\u884C\u5206\u6790\uFF1A
1226
1451
 
1227
- URL: ${url}
1228
- \u7C7B\u578B: ${type}
1452
+ ### 1. \u4E1A\u52A1\u529F\u80FD\u5206\u6790\uFF08\u91CD\u70B9\uFF09
1453
+ - \u6838\u5FC3\u4E1A\u52A1\u529F\u80FD\u662F\u4EC0\u4E48\uFF1F\uFF08\u8BE6\u7EC6\u63CF\u8FF0\u7528\u6237\u80FD\u505A\u4EC0\u4E48\uFF09
1454
+ - \u4E1A\u52A1\u6D41\u7A0B\u662F\u4EC0\u4E48\uFF1F\uFF08\u7528\u6237\u64CD\u4F5C\u6B65\u9AA4\uFF09
1455
+ - \u4E1A\u52A1\u89C4\u5219\u662F\u4EC0\u4E48\uFF1F\uFF08\u8F93\u5165\u9650\u5236\u3001\u8BA1\u7B97\u89C4\u5219\u7B49\uFF09
1229
1456
 
1230
- \u5185\u5BB9\u6458\u8981:
1231
- ${content.slice(0, 5e3)}
1457
+ ### 2. UI/UX \u7ED3\u6784\u5206\u6790
1458
+ - \u9875\u9762\u5E03\u5C40\u7ED3\u6784\uFF08\u4E0D\u6D89\u53CA\u5177\u4F53\u6280\u672F\u5B9E\u73B0\uFF09
1459
+ - \u4E3B\u8981\u7EC4\u4EF6/\u6A21\u5757\u6709\u54EA\u4E9B\uFF1F
1460
+ - \u4EA4\u4E92\u65B9\u5F0F\uFF08\u70B9\u51FB\u3001\u8F93\u5165\u3001\u62D6\u62FD\u7B49\uFF09
1232
1461
 
1233
- ${typePrompts[type]}
1462
+ ### 3. \u6570\u636E\u6A21\u578B\u5206\u6790
1463
+ - \u9700\u8981\u54EA\u4E9B\u6570\u636E\u5B57\u6BB5\uFF1F
1464
+ - \u6570\u636E\u4E4B\u95F4\u7684\u5173\u7CFB
1465
+ - \u6570\u636E\u6765\u6E90\uFF08\u7528\u6237\u8F93\u5165/\u8BA1\u7B97/API\uFF09
1234
1466
 
1235
- \u8BF7\u63D0\u53D6\uFF1A
1236
- 1. \u4E3B\u8981\u529F\u80FD\u70B9
1237
- 2. UI\u7EC4\u4EF6\u7ED3\u6784
1238
- 3. \u4EA4\u4E92\u65B9\u5F0F
1239
- 4. \u6570\u636E\u7ED3\u6784\uFF08\u5982\u679C\u6709\uFF09
1240
- 5. \u6280\u672F\u5B9E\u73B0\u5EFA\u8BAE
1467
+ ### 4. \u529F\u80FD\u62C6\u5206\u5EFA\u8BAE
1468
+ \u5C06\u529F\u80FD\u62C6\u5206\u4E3A\u53EF\u72EC\u7ACB\u5F00\u53D1\u7684\u4EFB\u52A1\uFF1A
1469
+ - \u4EFB\u52A1\u540D\u79F0\uFF08\u7B80\u77ED\u660E\u786E\uFF09
1470
+ - \u4EFB\u52A1\u63CF\u8FF0\uFF08\u4E1A\u52A1\u89D2\u5EA6\uFF09
1471
+ - \u9A8C\u6536\u6807\u51C6
1241
1472
 
1242
- \u4EE5\u7B80\u6D01\u7684\u8981\u70B9\u5F62\u5F0F\u8F93\u51FA\u3002
1473
+ \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
1474
+ \u6280\u672F\u5B9E\u73B0\u65B9\u6848\u7531\u9879\u76EE\u89C4\u8303\u51B3\u5B9A\uFF0C\u6B64\u5904\u4E0D\u6D89\u53CA\u3002
1243
1475
  `;
1476
+ const loader = new LoadingIndicator("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
1477
+ loader.start();
1244
1478
  try {
1245
1479
  const response = await ctx.modelService.sendMessage([
1246
1480
  { role: "user", content: prompt2 }
1247
- ], { temperature: 0.3, maxTokens: 2e3 });
1481
+ ], {
1482
+ temperature: 0.3,
1483
+ maxTokens: 4e3
1484
+ });
1485
+ loader.stop(chalk9__default.default.green(" \u2713 \u5206\u6790\u5B8C\u6210"));
1248
1486
  return response.content;
1249
- } catch {
1250
- return extractBasicInfo(content, type);
1487
+ } catch (error) {
1488
+ loader.stop();
1489
+ throw error;
1251
1490
  }
1252
1491
  }
1253
1492
  function extractBasicInfo(content, type) {
@@ -1288,10 +1527,43 @@ function getActiveSession() {
1288
1527
  function clearActiveSession() {
1289
1528
  activeSession = null;
1290
1529
  }
1291
- var MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
1530
+ var LoadingIndicator, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
1292
1531
  var init_new = __esm({
1293
1532
  "src/commands/new.ts"() {
1294
1533
  init_cjs_shims();
1534
+ LoadingIndicator = class {
1535
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1536
+ frameIndex = 0;
1537
+ interval = null;
1538
+ message;
1539
+ constructor(message) {
1540
+ this.message = message;
1541
+ }
1542
+ start() {
1543
+ process.stdout.write("\x1B[?25l");
1544
+ this.interval = setInterval(() => {
1545
+ const frame = this.frames[this.frameIndex];
1546
+ process.stdout.write(`\r${chalk9__default.default.cyan(frame)} ${this.message}...`);
1547
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
1548
+ }, 80);
1549
+ }
1550
+ update(message) {
1551
+ this.message = message;
1552
+ }
1553
+ stop(finalMessage) {
1554
+ if (this.interval) {
1555
+ clearInterval(this.interval);
1556
+ this.interval = null;
1557
+ }
1558
+ process.stdout.write("\x1B[?25h");
1559
+ if (finalMessage) {
1560
+ process.stdout.write(`\r${finalMessage}
1561
+ `);
1562
+ } else {
1563
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
1564
+ }
1565
+ }
1566
+ };
1295
1567
  MAX_FILE_SIZE2 = 1024 * 1024;
1296
1568
  COMPLEXITY_THRESHOLD = 6;
1297
1569
  CLARITY_THRESHOLD = 0.6;