@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.mjs CHANGED
@@ -155,7 +155,7 @@ async function executeWorkflow(ctx) {
155
155
  for (const url of urls) {
156
156
  lines.push(chalk9.gray(` \u{1F4CE} ${url}`));
157
157
  try {
158
- const resource = await fetchAndAnalyzeReference(url, ctx);
158
+ const resource = await fetchAndAnalyzeReference(url, ctx, activeSession.context || void 0);
159
159
  activeSession.referenceResources.push(resource);
160
160
  lines.push(chalk9.green(` \u2713 \u5DF2\u5206\u6790`));
161
161
  activeSession.refinedRequirement += `
@@ -194,12 +194,26 @@ ${resource.analysis}`;
194
194
  lines.push("");
195
195
  lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 5/9: BDD \u573A\u666F\u62C6\u89E3 \u2501\u2501\u2501"));
196
196
  lines.push("");
197
- activeSession.bddScenarios = generateBDDScenarios(
198
- activeSession.refinedRequirement,
199
- activeSession.context,
200
- activeSession.clarificationQuestions,
201
- activeSession.referenceResources
202
- );
197
+ const loader = new LoadingIndicator("AI \u6B63\u5728\u751F\u6210 BDD \u573A\u666F");
198
+ loader.start();
199
+ try {
200
+ activeSession.bddScenarios = await generateBDDScenariosWithAI(
201
+ activeSession.refinedRequirement,
202
+ activeSession.context,
203
+ activeSession.clarificationQuestions,
204
+ activeSession.referenceResources,
205
+ ctx
206
+ );
207
+ loader.stop(chalk9.green(" \u2713 BDD \u573A\u666F\u5DF2\u751F\u6210"));
208
+ } catch {
209
+ loader.stop(chalk9.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840 BDD \u751F\u6210"));
210
+ activeSession.bddScenarios = generateBDDScenarios(
211
+ activeSession.refinedRequirement,
212
+ activeSession.context,
213
+ activeSession.clarificationQuestions,
214
+ activeSession.referenceResources
215
+ );
216
+ }
203
217
  for (const scenario of activeSession.bddScenarios) {
204
218
  lines.push(chalk9.white(` Feature: ${scenario.feature}`));
205
219
  for (const s of scenario.scenarios.slice(0, 3)) {
@@ -246,7 +260,7 @@ ${resource.analysis}`;
246
260
  lines.push("");
247
261
  lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 7/9: TDD \u6D4B\u8BD5\u751F\u6210 \u2501\u2501\u2501"));
248
262
  lines.push("");
249
- activeSession.testFiles = await generateTests(ctx.options.workingDirectory, activeSession);
263
+ activeSession.testFiles = await generateTests(ctx.options.workingDirectory, activeSession, ctx);
250
264
  lines.push(chalk9.green(" \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210"));
251
265
  for (const file of activeSession.testFiles) {
252
266
  lines.push(chalk9.gray(` - ${file}`));
@@ -543,6 +557,8 @@ function getCategoryLabel(category) {
543
557
  async function executeDevelopment(ctx, session) {
544
558
  const workingDir = ctx.options.workingDirectory;
545
559
  const files = [];
560
+ const loader = new LoadingIndicator("AI \u6B63\u5728\u751F\u6210\u4EE3\u7801");
561
+ loader.start();
546
562
  try {
547
563
  const systemPrompt = buildDevelopmentPrompt(session);
548
564
  const messages = [
@@ -550,7 +566,12 @@ async function executeDevelopment(ctx, session) {
550
566
  role: "system",
551
567
  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
552
568
 
553
- \u8981\u6C42\uFF1A
569
+ \u26A0\uFE0F \u91CD\u8981\u89C4\u5219\uFF1A
570
+ 1. \u6280\u672F\u5B9E\u73B0\u5FC5\u987B\u4E25\u683C\u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u5F00\u53D1\u89C4\u8303
571
+ 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
572
+ 3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808\u548C\u6846\u67B6
573
+
574
+ \u4EE3\u7801\u8981\u6C42\uFF1A
554
575
  1. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
555
576
  2. \u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u4EE3\u7801\u98CE\u683C\u548C\u89C4\u8303
556
577
  3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808
@@ -562,19 +583,23 @@ async function executeDevelopment(ctx, session) {
562
583
  - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
563
584
  - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
564
585
 
565
- ${session.context?.devStandards ? `\u5F00\u53D1\u89C4\u8303\uFF1A
566
- ${session.context.devStandards.slice(0, 2e3)}` : ""}`
586
+ ${session.context?.devStandards ? `\u3010\u5FC5\u987B\u9075\u5FAA\u7684\u5F00\u53D1\u89C4\u8303\u3011
587
+ ${session.context.devStandards.slice(0, 2500)}` : ""}`
567
588
  },
568
589
  {
569
590
  role: "user",
570
591
  content: systemPrompt
571
592
  }
572
593
  ];
594
+ loader.update("\u6B63\u5728\u8C03\u7528 AI \u6A21\u578B");
573
595
  const response = await ctx.modelService.sendMessage(messages, {
574
596
  temperature: 0.3,
575
597
  maxTokens: 8e3,
576
- agent: "frontend-dev"
598
+ agent: "frontend-dev",
599
+ timeout: 18e4
600
+ // 3 分钟超时
577
601
  });
602
+ loader.update("\u6B63\u5728\u89E3\u6790\u4EE3\u7801");
578
603
  const codeBlocks = parseCodeBlocks(response.content);
579
604
  for (const block of codeBlocks) {
580
605
  const filePath = path5.join(workingDir, block.filename);
@@ -603,8 +628,10 @@ export function ${featureName.replace(/[^a-zA-Z0-9]/g, "")}() {
603
628
  await fs4.writeFile(filePath, stubCode, "utf-8");
604
629
  files.push(`src/features/${fileName}`);
605
630
  }
631
+ loader.stop(chalk9.green(` \u2713 \u5DF2\u751F\u6210 ${files.length} \u4E2A\u6587\u4EF6`));
606
632
  return { success: true, files };
607
633
  } catch (error) {
634
+ loader.stop();
608
635
  return {
609
636
  success: false,
610
637
  files: [],
@@ -888,36 +915,130 @@ function generateBDDScenarios(requirement, context, questions, references = [])
888
915
  }
889
916
  return scenarios;
890
917
  }
918
+ async function generateBDDScenariosWithAI(requirement, context, questions, references, ctx) {
919
+ 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
920
+
921
+ ## \u9700\u6C42\u63CF\u8FF0
922
+ ${requirement}
923
+
924
+ ## \u9879\u76EE\u4E0A\u4E0B\u6587
925
+ - \u6280\u672F\u6808: ${context.techStack?.join(", ") || "\u672A\u6307\u5B9A"}
926
+ - \u6846\u67B6: ${context.framework || "\u672A\u6307\u5B9A"}
927
+ ${context.devStandards ? `
928
+ ## \u9879\u76EE\u5F00\u53D1\u89C4\u8303\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09
929
+ ${context.devStandards.slice(0, 2e3)}
930
+ ` : ""}
931
+
932
+ ## \u6F84\u6E05\u95EE\u7B54
933
+ ${questions.filter((q) => q.answered).map((q) => `- Q: ${q.question}
934
+ A: ${q.answer}`).join("\n")}
935
+
936
+ ## \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
937
+ ${references.length > 0 ? references.map((r) => `### ${r.url}
938
+ ${r.analysis}`).join("\n\n") : "\u65E0"}
939
+
940
+ ## \u8981\u6C42
941
+ 1. \u6BCF\u4E2A\u529F\u80FD\u6A21\u5757\u751F\u6210\u4E00\u4E2A\u72EC\u7ACB\u7684 Feature
942
+ 2. \u6BCF\u4E2A Feature \u5305\u542B\u591A\u4E2A\u5177\u4F53\u7684 Scenario
943
+ 3. \u4F7F\u7528 Given-When-Then \u683C\u5F0F
944
+ 4. \u573A\u666F\u8981\u8986\u76D6: \u6B63\u5E38\u6D41\u7A0B\u3001\u8FB9\u754C\u60C5\u51B5\u3001\u5F02\u5E38\u5904\u7406
945
+ 5. \u573A\u666F\u8981\u5177\u4F53\u53EF\u6D4B\u8BD5\uFF0C\u805A\u7126\u4E1A\u52A1\u903B\u8F91
946
+ 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
947
+
948
+ ## \u8F93\u51FA\u683C\u5F0F (JSON)
949
+ \`\`\`json
950
+ [
951
+ {
952
+ "feature": "\u529F\u80FD\u540D\u79F0",
953
+ "description": "\u529F\u80FD\u63CF\u8FF0",
954
+ "scenarios": [
955
+ {
956
+ "name": "\u573A\u666F\u540D\u79F0",
957
+ "given": ["\u524D\u7F6E\u6761\u4EF61", "\u524D\u7F6E\u6761\u4EF62"],
958
+ "when": ["\u64CD\u4F5C1", "\u64CD\u4F5C2"],
959
+ "then": ["\u9884\u671F\u7ED3\u679C1", "\u9884\u671F\u7ED3\u679C2"]
960
+ }
961
+ ]
962
+ }
963
+ ]
964
+ \`\`\`
965
+
966
+ \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\uFF0C\u4E0D\u8981\u6709\u5176\u4ED6\u5185\u5BB9\u3002`;
967
+ const response = await ctx.modelService.sendMessage([
968
+ { role: "user", content: prompt2 }
969
+ ], {
970
+ temperature: 0.3,
971
+ maxTokens: 4e3,
972
+ timeout: 12e4
973
+ });
974
+ try {
975
+ const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
976
+ if (jsonMatch) {
977
+ return JSON.parse(jsonMatch[1].trim());
978
+ }
979
+ return JSON.parse(response.content);
980
+ } catch {
981
+ return generateBDDScenarios(requirement, context, questions, references);
982
+ }
983
+ }
891
984
  function extractFeaturesFromReference(ref) {
892
985
  const features = [];
893
- const analysis = ref.analysis.toLowerCase();
894
- if (analysis.includes("\u8F93\u5165") || analysis.includes("\u8868\u5355")) {
895
- features.push({
896
- title: "\u8F93\u5165\u8868\u5355",
897
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u8F93\u5165\u8868\u5355\u529F\u80FD",
898
- hasInput: true
899
- });
900
- }
901
- if (analysis.includes("\u6309\u94AE") || analysis.includes("\u64CD\u4F5C")) {
902
- features.push({
903
- title: "\u4EA4\u4E92\u6309\u94AE",
904
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6309\u94AE\u4EA4\u4E92",
905
- hasInput: false
906
- });
986
+ const analysis = ref.analysis;
987
+ const featureSection = analysis.match(/###?\s*4\.\s*功能拆分建议[\s\S]*?(?=###?\s*\d|$)/i);
988
+ if (featureSection) {
989
+ const taskMatches = featureSection[0].matchAll(/[-*]\s*\*\*([^*]+)\*\*[::]?\s*([^\n]+)/g);
990
+ for (const match of taskMatches) {
991
+ features.push({
992
+ title: match[1].trim(),
993
+ description: match[2].trim(),
994
+ hasInput: match[2].includes("\u8F93\u5165") || match[2].includes("\u8868\u5355") || match[2].includes("\u7528\u6237")
995
+ });
996
+ }
907
997
  }
908
- if (analysis.includes("\u8868\u683C") || analysis.includes("\u5217\u8868")) {
909
- features.push({
910
- title: "\u6570\u636E\u5217\u8868",
911
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6570\u636E\u5C55\u793A",
912
- hasInput: false
913
- });
998
+ const bizSection = analysis.match(/###?\s*1\.\s*业务功能分析[\s\S]*?(?=###?\s*\d|$)/i);
999
+ if (bizSection && features.length === 0) {
1000
+ const lines = bizSection[0].split("\n").filter((l) => l.trim().startsWith("-") || l.trim().startsWith("*"));
1001
+ for (const line of lines.slice(0, 5)) {
1002
+ const content = line.replace(/^[-*]\s*/, "").trim();
1003
+ if (content.length > 5) {
1004
+ features.push({
1005
+ title: content.slice(0, 20),
1006
+ description: content,
1007
+ hasInput: content.includes("\u8F93\u5165") || content.includes("\u586B\u5199")
1008
+ });
1009
+ }
1010
+ }
914
1011
  }
915
- if (analysis.includes("\u56FE\u8868") || analysis.includes("\u53EF\u89C6\u5316")) {
916
- features.push({
917
- title: "\u56FE\u8868\u5C55\u793A",
918
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u56FE\u8868\u53EF\u89C6\u5316",
919
- hasInput: false
920
- });
1012
+ if (features.length === 0) {
1013
+ const lowerAnalysis = analysis.toLowerCase();
1014
+ if (lowerAnalysis.includes("\u8F93\u5165") || lowerAnalysis.includes("\u8868\u5355")) {
1015
+ features.push({
1016
+ title: "\u8F93\u5165\u8868\u5355",
1017
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u8F93\u5165\u8868\u5355\u529F\u80FD",
1018
+ hasInput: true
1019
+ });
1020
+ }
1021
+ if (lowerAnalysis.includes("\u6309\u94AE") || lowerAnalysis.includes("\u64CD\u4F5C")) {
1022
+ features.push({
1023
+ title: "\u4EA4\u4E92\u6309\u94AE",
1024
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6309\u94AE\u4EA4\u4E92",
1025
+ hasInput: false
1026
+ });
1027
+ }
1028
+ if (lowerAnalysis.includes("\u8868\u683C") || lowerAnalysis.includes("\u5217\u8868")) {
1029
+ features.push({
1030
+ title: "\u6570\u636E\u5217\u8868",
1031
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6570\u636E\u5C55\u793A",
1032
+ hasInput: false
1033
+ });
1034
+ }
1035
+ if (lowerAnalysis.includes("\u56FE\u8868") || lowerAnalysis.includes("\u53EF\u89C6\u5316")) {
1036
+ features.push({
1037
+ title: "\u56FE\u8868\u5C55\u793A",
1038
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u56FE\u8868\u53EF\u89C6\u5316",
1039
+ hasInput: false
1040
+ });
1041
+ }
921
1042
  }
922
1043
  if (features.length === 0) {
923
1044
  features.push({
@@ -1081,33 +1202,119 @@ function formatSpecFile(session) {
1081
1202
  lines.push("**\u786E\u8BA4\u72B6\u6001**: \u23F3 \u7B49\u5F85\u786E\u8BA4");
1082
1203
  return lines.join("\n");
1083
1204
  }
1084
- async function generateTests(workingDir, session) {
1205
+ async function generateTests(workingDir, session, ctx) {
1085
1206
  const testDir = path5.join(workingDir, "tests");
1086
1207
  await fs4.mkdir(testDir, { recursive: true });
1087
1208
  const testFiles = [];
1088
- for (const scenario of session.bddScenarios) {
1089
- const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
1090
- const testPath = path5.join(testDir, `${testName}.test.ts`);
1091
- const content = generateTestFile(scenario);
1092
- await fs4.writeFile(testPath, content, "utf-8");
1093
- testFiles.push(`tests/${testName}.test.ts`);
1209
+ if (ctx?.modelService) {
1210
+ for (const scenario of session.bddScenarios) {
1211
+ const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
1212
+ const testPath = path5.join(testDir, `${testName}.test.ts`);
1213
+ const loader = new LoadingIndicator(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
1214
+ loader.start();
1215
+ try {
1216
+ const content = await generateTestFileWithAI(scenario, session, ctx);
1217
+ await fs4.writeFile(testPath, content, "utf-8");
1218
+ testFiles.push(`tests/${testName}.test.ts`);
1219
+ loader.stop(chalk9.green(` \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210`));
1220
+ } catch {
1221
+ const content = generateTestFile(scenario, session);
1222
+ await fs4.writeFile(testPath, content, "utf-8");
1223
+ testFiles.push(`tests/${testName}.test.ts`);
1224
+ loader.stop(chalk9.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840\u6D4B\u8BD5\u6A21\u677F"));
1225
+ }
1226
+ }
1227
+ } else {
1228
+ for (const scenario of session.bddScenarios) {
1229
+ const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
1230
+ const testPath = path5.join(testDir, `${testName}.test.ts`);
1231
+ const content = generateTestFile(scenario, session);
1232
+ await fs4.writeFile(testPath, content, "utf-8");
1233
+ testFiles.push(`tests/${testName}.test.ts`);
1234
+ }
1094
1235
  }
1095
1236
  return testFiles;
1096
1237
  }
1097
- function generateTestFile(scenario) {
1238
+ async function generateTestFileWithAI(scenario, session, ctx) {
1239
+ 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
1240
+
1241
+ ## \u529F\u80FD\u540D\u79F0
1242
+ ${scenario.feature}
1243
+
1244
+ ## BDD \u573A\u666F
1245
+ ${scenario.scenarios.map((s) => `
1246
+ ### ${s.name}
1247
+ - Given: ${s.given.join(", ")}
1248
+ - When: ${s.when.join(", ")}
1249
+ - Then: ${s.then.join(", ")}
1250
+ `).join("\n")}
1251
+
1252
+ ## \u9879\u76EE\u4E0A\u4E0B\u6587
1253
+ - \u6280\u672F\u6808: ${session.context?.techStack?.join(", ") || "TypeScript"}
1254
+ - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
1255
+
1256
+ ## \u8981\u6C42
1257
+ 1. \u4F7F\u7528 vitest \u6D4B\u8BD5\u6846\u67B6 (describe, it, expect, beforeEach \u7B49)
1258
+ 2. \u6BCF\u4E2A\u573A\u666F\u751F\u6210\u4E00\u4E2A\u72EC\u7ACB\u7684\u6D4B\u8BD5\u7528\u4F8B
1259
+ 3. \u6D4B\u8BD5\u4EE3\u7801\u8981\u5B8C\u6574\u53EF\u8FD0\u884C\uFF0C\u5305\u542B\u5FC5\u8981\u7684 mock \u548C setup
1260
+ 4. \u4F7F\u7528\u4E2D\u6587\u6CE8\u91CA\u8BF4\u660E\u6D4B\u8BD5\u610F\u56FE
1261
+ 5. \u6D4B\u8BD5\u8981\u8986\u76D6\u6B63\u5E38\u6D41\u7A0B\u548C\u8FB9\u754C\u60C5\u51B5
1262
+
1263
+ \u8BF7\u76F4\u63A5\u8F93\u51FA\u6D4B\u8BD5\u4EE3\u7801\uFF0C\u4E0D\u9700\u8981\u89E3\u91CA\u3002`;
1264
+ const response = await ctx.modelService.sendMessage([
1265
+ { role: "user", content: prompt2 }
1266
+ ], {
1267
+ temperature: 0.3,
1268
+ maxTokens: 4e3
1269
+ });
1270
+ const codeMatch = response.content.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
1271
+ if (codeMatch) {
1272
+ return codeMatch[1].trim();
1273
+ }
1274
+ return response.content;
1275
+ }
1276
+ function generateTestFile(scenario, session) {
1098
1277
  const lines = [];
1099
- lines.push(`import { describe, it, expect } from 'vitest';`);
1278
+ lines.push(`import { describe, it, expect, beforeEach } from 'vitest';`);
1100
1279
  lines.push("");
1280
+ lines.push(`/**`);
1281
+ lines.push(` * ${scenario.feature} \u529F\u80FD\u6D4B\u8BD5`);
1282
+ lines.push(` * `);
1283
+ lines.push(` * BDD \u573A\u666F\u6570\u91CF: ${scenario.scenarios.length}`);
1284
+ if (session?.context?.techStack) {
1285
+ lines.push(` * \u6280\u672F\u6808: ${session.context.techStack.join(", ")}`);
1286
+ }
1287
+ lines.push(` */`);
1101
1288
  lines.push(`describe('${scenario.feature}', () => {`);
1102
1289
  for (const s of scenario.scenarios) {
1103
- lines.push(` it('${s.name}', () => {`);
1104
- lines.push(` // Given: ${s.given.join(", ")}`);
1105
- lines.push(` // When: ${s.when.join(", ")}`);
1106
- lines.push(` // Then: ${s.then.join(", ")}`);
1107
- lines.push(` expect(true).toBe(true); // TODO: \u5B9E\u73B0\u6D4B\u8BD5`);
1108
- lines.push(` });`);
1109
1290
  lines.push("");
1291
+ lines.push(` /**`);
1292
+ lines.push(` * \u573A\u666F: ${s.name}`);
1293
+ lines.push(` * Given: ${s.given.join(", ")}`);
1294
+ lines.push(` * When: ${s.when.join(", ")}`);
1295
+ lines.push(` * Then: ${s.then.join(", ")}`);
1296
+ lines.push(` */`);
1297
+ lines.push(` it('${s.name}', async () => {`);
1298
+ lines.push(` // Arrange (Given)`);
1299
+ for (const g of s.given) {
1300
+ lines.push(` // ${g}`);
1301
+ }
1302
+ lines.push(` const input = {}; // TODO: \u8BBE\u7F6E\u521D\u59CB\u72B6\u6001`);
1303
+ lines.push("");
1304
+ lines.push(` // Act (When)`);
1305
+ for (const w of s.when) {
1306
+ lines.push(` // ${w}`);
1307
+ }
1308
+ lines.push(` const result = {}; // TODO: \u6267\u884C\u64CD\u4F5C`);
1309
+ lines.push("");
1310
+ lines.push(` // Assert (Then)`);
1311
+ for (const t of s.then) {
1312
+ lines.push(` // ${t}`);
1313
+ }
1314
+ lines.push(` expect(result).toBeDefined(); // TODO: \u5B8C\u5584\u65AD\u8A00`);
1315
+ lines.push(` });`);
1110
1316
  }
1317
+ lines.push("");
1111
1318
  lines.push(`});`);
1112
1319
  return lines.join("\n");
1113
1320
  }
@@ -1153,7 +1360,7 @@ function extractUrls(text) {
1153
1360
  const matches = text.match(urlRegex);
1154
1361
  return matches ? [...new Set(matches)] : [];
1155
1362
  }
1156
- async function fetchAndAnalyzeReference(url, ctx) {
1363
+ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
1157
1364
  const type = detectResourceType(url);
1158
1365
  let content = "";
1159
1366
  let analysis = "";
@@ -1168,7 +1375,7 @@ async function fetchAndAnalyzeReference(url, ctx) {
1168
1375
  }
1169
1376
  content = await response.text();
1170
1377
  if (ctx.modelService.getCurrentModel()) {
1171
- analysis = await analyzeReferenceContent(url, content, type, ctx);
1378
+ analysis = await analyzeReferenceContent(url, content, type, ctx, projectContext);
1172
1379
  } else {
1173
1380
  analysis = extractBasicInfo(content, type);
1174
1381
  }
@@ -1189,40 +1396,72 @@ function detectResourceType(url) {
1189
1396
  }
1190
1397
  return "webpage";
1191
1398
  }
1192
- async function analyzeReferenceContent(url, content, type, ctx) {
1193
- const typePrompts = {
1194
- webpage: "\u5206\u6790\u8FD9\u4E2A\u7F51\u9875\u7684\u529F\u80FD\u3001UI\u7EC4\u4EF6\u548C\u4EA4\u4E92\u65B9\u5F0F",
1195
- design: "\u5206\u6790\u8FD9\u4E2A\u8BBE\u8BA1\u7A3F\u7684\u5E03\u5C40\u3001\u7EC4\u4EF6\u548C\u6837\u5F0F",
1196
- image: "\u63CF\u8FF0\u8FD9\u4E2A\u56FE\u7247\u7684\u5185\u5BB9\u548C\u8BBE\u8BA1\u5143\u7D20",
1197
- api: "\u5206\u6790\u8FD9\u4E2AAPI\u7684\u7ED3\u6784\u548C\u53C2\u6570"
1198
- };
1399
+ async function analyzeReferenceContent(url, content, type, ctx, projectContext) {
1199
1400
  const prompt2 = `
1200
- \u8BF7\u5206\u6790\u4EE5\u4E0B\u53C2\u8003\u8D44\u6E90\u7684 URL\uFF0C\u63D0\u53D6\u5BF9\u5F00\u53D1\u6709\u7528\u7684\u4FE1\u606F\uFF1A
1401
+ \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
1402
+
1403
+ ## \u91CD\u8981\u8BF4\u660E
1404
+ \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
1405
+ \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
1406
+
1407
+ ${projectContext ? `## \u5F53\u524D\u9879\u76EE\u89C4\u8303
1408
+ - \u9879\u76EE\u540D\u79F0: ${projectContext.name}
1409
+ - \u6280\u672F\u6808: ${projectContext.techStack.join(", ") || "\u672A\u6307\u5B9A"}
1410
+ - \u6846\u67B6: ${projectContext.framework || "\u672A\u6307\u5B9A"}
1411
+ - \u5F00\u53D1\u89C4\u8303\u6458\u8981: ${projectContext.devStandards?.slice(0, 1500) || "\u672A\u6307\u5B9A"}
1412
+ ` : ""}
1413
+
1414
+ ## \u53C2\u8003\u8D44\u6E90\u4FE1\u606F
1415
+ - URL: ${url}
1416
+ - \u7C7B\u578B: ${type}
1417
+
1418
+ ## \u7F51\u9875\u5185\u5BB9
1419
+ \`\`\`html
1420
+ ${content.slice(0, 8e3)}
1421
+ \`\`\`
1422
+
1423
+ ## \u5206\u6790\u8981\u6C42
1424
+
1425
+ \u8BF7\u6309\u7167\u4EE5\u4E0B\u7ED3\u6784\u8FDB\u884C\u5206\u6790\uFF1A
1201
1426
 
1202
- URL: ${url}
1203
- \u7C7B\u578B: ${type}
1427
+ ### 1. \u4E1A\u52A1\u529F\u80FD\u5206\u6790\uFF08\u91CD\u70B9\uFF09
1428
+ - \u6838\u5FC3\u4E1A\u52A1\u529F\u80FD\u662F\u4EC0\u4E48\uFF1F\uFF08\u8BE6\u7EC6\u63CF\u8FF0\u7528\u6237\u80FD\u505A\u4EC0\u4E48\uFF09
1429
+ - \u4E1A\u52A1\u6D41\u7A0B\u662F\u4EC0\u4E48\uFF1F\uFF08\u7528\u6237\u64CD\u4F5C\u6B65\u9AA4\uFF09
1430
+ - \u4E1A\u52A1\u89C4\u5219\u662F\u4EC0\u4E48\uFF1F\uFF08\u8F93\u5165\u9650\u5236\u3001\u8BA1\u7B97\u89C4\u5219\u7B49\uFF09
1204
1431
 
1205
- \u5185\u5BB9\u6458\u8981:
1206
- ${content.slice(0, 5e3)}
1432
+ ### 2. UI/UX \u7ED3\u6784\u5206\u6790
1433
+ - \u9875\u9762\u5E03\u5C40\u7ED3\u6784\uFF08\u4E0D\u6D89\u53CA\u5177\u4F53\u6280\u672F\u5B9E\u73B0\uFF09
1434
+ - \u4E3B\u8981\u7EC4\u4EF6/\u6A21\u5757\u6709\u54EA\u4E9B\uFF1F
1435
+ - \u4EA4\u4E92\u65B9\u5F0F\uFF08\u70B9\u51FB\u3001\u8F93\u5165\u3001\u62D6\u62FD\u7B49\uFF09
1207
1436
 
1208
- ${typePrompts[type]}
1437
+ ### 3. \u6570\u636E\u6A21\u578B\u5206\u6790
1438
+ - \u9700\u8981\u54EA\u4E9B\u6570\u636E\u5B57\u6BB5\uFF1F
1439
+ - \u6570\u636E\u4E4B\u95F4\u7684\u5173\u7CFB
1440
+ - \u6570\u636E\u6765\u6E90\uFF08\u7528\u6237\u8F93\u5165/\u8BA1\u7B97/API\uFF09
1209
1441
 
1210
- \u8BF7\u63D0\u53D6\uFF1A
1211
- 1. \u4E3B\u8981\u529F\u80FD\u70B9
1212
- 2. UI\u7EC4\u4EF6\u7ED3\u6784
1213
- 3. \u4EA4\u4E92\u65B9\u5F0F
1214
- 4. \u6570\u636E\u7ED3\u6784\uFF08\u5982\u679C\u6709\uFF09
1215
- 5. \u6280\u672F\u5B9E\u73B0\u5EFA\u8BAE
1442
+ ### 4. \u529F\u80FD\u62C6\u5206\u5EFA\u8BAE
1443
+ \u5C06\u529F\u80FD\u62C6\u5206\u4E3A\u53EF\u72EC\u7ACB\u5F00\u53D1\u7684\u4EFB\u52A1\uFF1A
1444
+ - \u4EFB\u52A1\u540D\u79F0\uFF08\u7B80\u77ED\u660E\u786E\uFF09
1445
+ - \u4EFB\u52A1\u63CF\u8FF0\uFF08\u4E1A\u52A1\u89D2\u5EA6\uFF09
1446
+ - \u9A8C\u6536\u6807\u51C6
1216
1447
 
1217
- \u4EE5\u7B80\u6D01\u7684\u8981\u70B9\u5F62\u5F0F\u8F93\u51FA\u3002
1448
+ \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
1449
+ \u6280\u672F\u5B9E\u73B0\u65B9\u6848\u7531\u9879\u76EE\u89C4\u8303\u51B3\u5B9A\uFF0C\u6B64\u5904\u4E0D\u6D89\u53CA\u3002
1218
1450
  `;
1451
+ const loader = new LoadingIndicator("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
1452
+ loader.start();
1219
1453
  try {
1220
1454
  const response = await ctx.modelService.sendMessage([
1221
1455
  { role: "user", content: prompt2 }
1222
- ], { temperature: 0.3, maxTokens: 2e3 });
1456
+ ], {
1457
+ temperature: 0.3,
1458
+ maxTokens: 4e3
1459
+ });
1460
+ loader.stop(chalk9.green(" \u2713 \u5206\u6790\u5B8C\u6210"));
1223
1461
  return response.content;
1224
- } catch {
1225
- return extractBasicInfo(content, type);
1462
+ } catch (error) {
1463
+ loader.stop();
1464
+ throw error;
1226
1465
  }
1227
1466
  }
1228
1467
  function extractBasicInfo(content, type) {
@@ -1263,10 +1502,43 @@ function getActiveSession() {
1263
1502
  function clearActiveSession() {
1264
1503
  activeSession = null;
1265
1504
  }
1266
- var MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
1505
+ var LoadingIndicator, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
1267
1506
  var init_new = __esm({
1268
1507
  "src/commands/new.ts"() {
1269
1508
  init_esm_shims();
1509
+ LoadingIndicator = class {
1510
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1511
+ frameIndex = 0;
1512
+ interval = null;
1513
+ message;
1514
+ constructor(message) {
1515
+ this.message = message;
1516
+ }
1517
+ start() {
1518
+ process.stdout.write("\x1B[?25l");
1519
+ this.interval = setInterval(() => {
1520
+ const frame = this.frames[this.frameIndex];
1521
+ process.stdout.write(`\r${chalk9.cyan(frame)} ${this.message}...`);
1522
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
1523
+ }, 80);
1524
+ }
1525
+ update(message) {
1526
+ this.message = message;
1527
+ }
1528
+ stop(finalMessage) {
1529
+ if (this.interval) {
1530
+ clearInterval(this.interval);
1531
+ this.interval = null;
1532
+ }
1533
+ process.stdout.write("\x1B[?25h");
1534
+ if (finalMessage) {
1535
+ process.stdout.write(`\r${finalMessage}
1536
+ `);
1537
+ } else {
1538
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
1539
+ }
1540
+ }
1541
+ };
1270
1542
  MAX_FILE_SIZE2 = 1024 * 1024;
1271
1543
  COMPLEXITY_THRESHOLD = 6;
1272
1544
  CLARITY_THRESHOLD = 0.6;