@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/cli/index.js CHANGED
@@ -1510,7 +1510,7 @@ async function executeWorkflow(ctx) {
1510
1510
  for (const url of urls) {
1511
1511
  lines.push(chalk9__default.default.gray(` \u{1F4CE} ${url}`));
1512
1512
  try {
1513
- const resource = await fetchAndAnalyzeReference(url, ctx);
1513
+ const resource = await fetchAndAnalyzeReference(url, ctx, activeSession.context || void 0);
1514
1514
  activeSession.referenceResources.push(resource);
1515
1515
  lines.push(chalk9__default.default.green(` \u2713 \u5DF2\u5206\u6790`));
1516
1516
  activeSession.refinedRequirement += `
@@ -1549,12 +1549,26 @@ ${resource.analysis}`;
1549
1549
  lines.push("");
1550
1550
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 5/9: BDD \u573A\u666F\u62C6\u89E3 \u2501\u2501\u2501"));
1551
1551
  lines.push("");
1552
- activeSession.bddScenarios = generateBDDScenarios(
1553
- activeSession.refinedRequirement,
1554
- activeSession.context,
1555
- activeSession.clarificationQuestions,
1556
- activeSession.referenceResources
1557
- );
1552
+ const loader = new LoadingIndicator("AI \u6B63\u5728\u751F\u6210 BDD \u573A\u666F");
1553
+ loader.start();
1554
+ try {
1555
+ activeSession.bddScenarios = await generateBDDScenariosWithAI(
1556
+ activeSession.refinedRequirement,
1557
+ activeSession.context,
1558
+ activeSession.clarificationQuestions,
1559
+ activeSession.referenceResources,
1560
+ ctx
1561
+ );
1562
+ loader.stop(chalk9__default.default.green(" \u2713 BDD \u573A\u666F\u5DF2\u751F\u6210"));
1563
+ } catch {
1564
+ loader.stop(chalk9__default.default.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840 BDD \u751F\u6210"));
1565
+ activeSession.bddScenarios = generateBDDScenarios(
1566
+ activeSession.refinedRequirement,
1567
+ activeSession.context,
1568
+ activeSession.clarificationQuestions,
1569
+ activeSession.referenceResources
1570
+ );
1571
+ }
1558
1572
  for (const scenario of activeSession.bddScenarios) {
1559
1573
  lines.push(chalk9__default.default.white(` Feature: ${scenario.feature}`));
1560
1574
  for (const s of scenario.scenarios.slice(0, 3)) {
@@ -1601,7 +1615,7 @@ ${resource.analysis}`;
1601
1615
  lines.push("");
1602
1616
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 7/9: TDD \u6D4B\u8BD5\u751F\u6210 \u2501\u2501\u2501"));
1603
1617
  lines.push("");
1604
- activeSession.testFiles = await generateTests(ctx.options.workingDirectory, activeSession);
1618
+ activeSession.testFiles = await generateTests(ctx.options.workingDirectory, activeSession, ctx);
1605
1619
  lines.push(chalk9__default.default.green(" \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210"));
1606
1620
  for (const file of activeSession.testFiles) {
1607
1621
  lines.push(chalk9__default.default.gray(` - ${file}`));
@@ -1898,6 +1912,8 @@ function getCategoryLabel(category) {
1898
1912
  async function executeDevelopment(ctx, session) {
1899
1913
  const workingDir = ctx.options.workingDirectory;
1900
1914
  const files = [];
1915
+ const loader = new LoadingIndicator("AI \u6B63\u5728\u751F\u6210\u4EE3\u7801");
1916
+ loader.start();
1901
1917
  try {
1902
1918
  const systemPrompt = buildDevelopmentPrompt(session);
1903
1919
  const messages = [
@@ -1905,7 +1921,12 @@ async function executeDevelopment(ctx, session) {
1905
1921
  role: "system",
1906
1922
  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
1907
1923
 
1908
- \u8981\u6C42\uFF1A
1924
+ \u26A0\uFE0F \u91CD\u8981\u89C4\u5219\uFF1A
1925
+ 1. \u6280\u672F\u5B9E\u73B0\u5FC5\u987B\u4E25\u683C\u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u5F00\u53D1\u89C4\u8303
1926
+ 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
1927
+ 3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808\u548C\u6846\u67B6
1928
+
1929
+ \u4EE3\u7801\u8981\u6C42\uFF1A
1909
1930
  1. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
1910
1931
  2. \u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u4EE3\u7801\u98CE\u683C\u548C\u89C4\u8303
1911
1932
  3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808
@@ -1917,19 +1938,23 @@ async function executeDevelopment(ctx, session) {
1917
1938
  - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
1918
1939
  - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
1919
1940
 
1920
- ${session.context?.devStandards ? `\u5F00\u53D1\u89C4\u8303\uFF1A
1921
- ${session.context.devStandards.slice(0, 2e3)}` : ""}`
1941
+ ${session.context?.devStandards ? `\u3010\u5FC5\u987B\u9075\u5FAA\u7684\u5F00\u53D1\u89C4\u8303\u3011
1942
+ ${session.context.devStandards.slice(0, 2500)}` : ""}`
1922
1943
  },
1923
1944
  {
1924
1945
  role: "user",
1925
1946
  content: systemPrompt
1926
1947
  }
1927
1948
  ];
1949
+ loader.update("\u6B63\u5728\u8C03\u7528 AI \u6A21\u578B");
1928
1950
  const response = await ctx.modelService.sendMessage(messages, {
1929
1951
  temperature: 0.3,
1930
1952
  maxTokens: 8e3,
1931
- agent: "frontend-dev"
1953
+ agent: "frontend-dev",
1954
+ timeout: 18e4
1955
+ // 3 分钟超时
1932
1956
  });
1957
+ loader.update("\u6B63\u5728\u89E3\u6790\u4EE3\u7801");
1933
1958
  const codeBlocks = parseCodeBlocks(response.content);
1934
1959
  for (const block of codeBlocks) {
1935
1960
  const filePath = path5__namespace.join(workingDir, block.filename);
@@ -1958,8 +1983,10 @@ export function ${featureName.replace(/[^a-zA-Z0-9]/g, "")}() {
1958
1983
  await fs5__namespace.writeFile(filePath, stubCode, "utf-8");
1959
1984
  files.push(`src/features/${fileName}`);
1960
1985
  }
1986
+ loader.stop(chalk9__default.default.green(` \u2713 \u5DF2\u751F\u6210 ${files.length} \u4E2A\u6587\u4EF6`));
1961
1987
  return { success: true, files };
1962
1988
  } catch (error) {
1989
+ loader.stop();
1963
1990
  return {
1964
1991
  success: false,
1965
1992
  files: [],
@@ -2243,36 +2270,130 @@ function generateBDDScenarios(requirement, context, questions, references = [])
2243
2270
  }
2244
2271
  return scenarios;
2245
2272
  }
2273
+ async function generateBDDScenariosWithAI(requirement, context, questions, references, ctx) {
2274
+ 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
2275
+
2276
+ ## \u9700\u6C42\u63CF\u8FF0
2277
+ ${requirement}
2278
+
2279
+ ## \u9879\u76EE\u4E0A\u4E0B\u6587
2280
+ - \u6280\u672F\u6808: ${context.techStack?.join(", ") || "\u672A\u6307\u5B9A"}
2281
+ - \u6846\u67B6: ${context.framework || "\u672A\u6307\u5B9A"}
2282
+ ${context.devStandards ? `
2283
+ ## \u9879\u76EE\u5F00\u53D1\u89C4\u8303\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09
2284
+ ${context.devStandards.slice(0, 2e3)}
2285
+ ` : ""}
2286
+
2287
+ ## \u6F84\u6E05\u95EE\u7B54
2288
+ ${questions.filter((q) => q.answered).map((q) => `- Q: ${q.question}
2289
+ A: ${q.answer}`).join("\n")}
2290
+
2291
+ ## \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
2292
+ ${references.length > 0 ? references.map((r) => `### ${r.url}
2293
+ ${r.analysis}`).join("\n\n") : "\u65E0"}
2294
+
2295
+ ## \u8981\u6C42
2296
+ 1. \u6BCF\u4E2A\u529F\u80FD\u6A21\u5757\u751F\u6210\u4E00\u4E2A\u72EC\u7ACB\u7684 Feature
2297
+ 2. \u6BCF\u4E2A Feature \u5305\u542B\u591A\u4E2A\u5177\u4F53\u7684 Scenario
2298
+ 3. \u4F7F\u7528 Given-When-Then \u683C\u5F0F
2299
+ 4. \u573A\u666F\u8981\u8986\u76D6: \u6B63\u5E38\u6D41\u7A0B\u3001\u8FB9\u754C\u60C5\u51B5\u3001\u5F02\u5E38\u5904\u7406
2300
+ 5. \u573A\u666F\u8981\u5177\u4F53\u53EF\u6D4B\u8BD5\uFF0C\u805A\u7126\u4E1A\u52A1\u903B\u8F91
2301
+ 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
2302
+
2303
+ ## \u8F93\u51FA\u683C\u5F0F (JSON)
2304
+ \`\`\`json
2305
+ [
2306
+ {
2307
+ "feature": "\u529F\u80FD\u540D\u79F0",
2308
+ "description": "\u529F\u80FD\u63CF\u8FF0",
2309
+ "scenarios": [
2310
+ {
2311
+ "name": "\u573A\u666F\u540D\u79F0",
2312
+ "given": ["\u524D\u7F6E\u6761\u4EF61", "\u524D\u7F6E\u6761\u4EF62"],
2313
+ "when": ["\u64CD\u4F5C1", "\u64CD\u4F5C2"],
2314
+ "then": ["\u9884\u671F\u7ED3\u679C1", "\u9884\u671F\u7ED3\u679C2"]
2315
+ }
2316
+ ]
2317
+ }
2318
+ ]
2319
+ \`\`\`
2320
+
2321
+ \u8BF7\u76F4\u63A5\u8F93\u51FA JSON \u6570\u7EC4\uFF0C\u4E0D\u8981\u6709\u5176\u4ED6\u5185\u5BB9\u3002`;
2322
+ const response = await ctx.modelService.sendMessage([
2323
+ { role: "user", content: prompt2 }
2324
+ ], {
2325
+ temperature: 0.3,
2326
+ maxTokens: 4e3,
2327
+ timeout: 12e4
2328
+ });
2329
+ try {
2330
+ const jsonMatch = response.content.match(/```json\s*([\s\S]*?)```/);
2331
+ if (jsonMatch) {
2332
+ return JSON.parse(jsonMatch[1].trim());
2333
+ }
2334
+ return JSON.parse(response.content);
2335
+ } catch {
2336
+ return generateBDDScenarios(requirement, context, questions, references);
2337
+ }
2338
+ }
2246
2339
  function extractFeaturesFromReference(ref) {
2247
2340
  const features = [];
2248
- const analysis = ref.analysis.toLowerCase();
2249
- if (analysis.includes("\u8F93\u5165") || analysis.includes("\u8868\u5355")) {
2250
- features.push({
2251
- title: "\u8F93\u5165\u8868\u5355",
2252
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u8F93\u5165\u8868\u5355\u529F\u80FD",
2253
- hasInput: true
2254
- });
2255
- }
2256
- if (analysis.includes("\u6309\u94AE") || analysis.includes("\u64CD\u4F5C")) {
2257
- features.push({
2258
- title: "\u4EA4\u4E92\u6309\u94AE",
2259
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6309\u94AE\u4EA4\u4E92",
2260
- hasInput: false
2261
- });
2341
+ const analysis = ref.analysis;
2342
+ const featureSection = analysis.match(/###?\s*4\.\s*功能拆分建议[\s\S]*?(?=###?\s*\d|$)/i);
2343
+ if (featureSection) {
2344
+ const taskMatches = featureSection[0].matchAll(/[-*]\s*\*\*([^*]+)\*\*[::]?\s*([^\n]+)/g);
2345
+ for (const match of taskMatches) {
2346
+ features.push({
2347
+ title: match[1].trim(),
2348
+ description: match[2].trim(),
2349
+ hasInput: match[2].includes("\u8F93\u5165") || match[2].includes("\u8868\u5355") || match[2].includes("\u7528\u6237")
2350
+ });
2351
+ }
2262
2352
  }
2263
- if (analysis.includes("\u8868\u683C") || analysis.includes("\u5217\u8868")) {
2264
- features.push({
2265
- title: "\u6570\u636E\u5217\u8868",
2266
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6570\u636E\u5C55\u793A",
2267
- hasInput: false
2268
- });
2353
+ const bizSection = analysis.match(/###?\s*1\.\s*业务功能分析[\s\S]*?(?=###?\s*\d|$)/i);
2354
+ if (bizSection && features.length === 0) {
2355
+ const lines = bizSection[0].split("\n").filter((l) => l.trim().startsWith("-") || l.trim().startsWith("*"));
2356
+ for (const line of lines.slice(0, 5)) {
2357
+ const content = line.replace(/^[-*]\s*/, "").trim();
2358
+ if (content.length > 5) {
2359
+ features.push({
2360
+ title: content.slice(0, 20),
2361
+ description: content,
2362
+ hasInput: content.includes("\u8F93\u5165") || content.includes("\u586B\u5199")
2363
+ });
2364
+ }
2365
+ }
2269
2366
  }
2270
- if (analysis.includes("\u56FE\u8868") || analysis.includes("\u53EF\u89C6\u5316")) {
2271
- features.push({
2272
- title: "\u56FE\u8868\u5C55\u793A",
2273
- description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u56FE\u8868\u53EF\u89C6\u5316",
2274
- hasInput: false
2275
- });
2367
+ if (features.length === 0) {
2368
+ const lowerAnalysis = analysis.toLowerCase();
2369
+ if (lowerAnalysis.includes("\u8F93\u5165") || lowerAnalysis.includes("\u8868\u5355")) {
2370
+ features.push({
2371
+ title: "\u8F93\u5165\u8868\u5355",
2372
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u8F93\u5165\u8868\u5355\u529F\u80FD",
2373
+ hasInput: true
2374
+ });
2375
+ }
2376
+ if (lowerAnalysis.includes("\u6309\u94AE") || lowerAnalysis.includes("\u64CD\u4F5C")) {
2377
+ features.push({
2378
+ title: "\u4EA4\u4E92\u6309\u94AE",
2379
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6309\u94AE\u4EA4\u4E92",
2380
+ hasInput: false
2381
+ });
2382
+ }
2383
+ if (lowerAnalysis.includes("\u8868\u683C") || lowerAnalysis.includes("\u5217\u8868")) {
2384
+ features.push({
2385
+ title: "\u6570\u636E\u5217\u8868",
2386
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u6570\u636E\u5C55\u793A",
2387
+ hasInput: false
2388
+ });
2389
+ }
2390
+ if (lowerAnalysis.includes("\u56FE\u8868") || lowerAnalysis.includes("\u53EF\u89C6\u5316")) {
2391
+ features.push({
2392
+ title: "\u56FE\u8868\u5C55\u793A",
2393
+ description: "\u53C2\u8003\u754C\u9762\u4E2D\u7684\u56FE\u8868\u53EF\u89C6\u5316",
2394
+ hasInput: false
2395
+ });
2396
+ }
2276
2397
  }
2277
2398
  if (features.length === 0) {
2278
2399
  features.push({
@@ -2436,33 +2557,119 @@ function formatSpecFile(session) {
2436
2557
  lines.push("**\u786E\u8BA4\u72B6\u6001**: \u23F3 \u7B49\u5F85\u786E\u8BA4");
2437
2558
  return lines.join("\n");
2438
2559
  }
2439
- async function generateTests(workingDir, session) {
2560
+ async function generateTests(workingDir, session, ctx) {
2440
2561
  const testDir = path5__namespace.join(workingDir, "tests");
2441
2562
  await fs5__namespace.mkdir(testDir, { recursive: true });
2442
2563
  const testFiles = [];
2443
- for (const scenario of session.bddScenarios) {
2444
- const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
2445
- const testPath = path5__namespace.join(testDir, `${testName}.test.ts`);
2446
- const content = generateTestFile(scenario);
2447
- await fs5__namespace.writeFile(testPath, content, "utf-8");
2448
- testFiles.push(`tests/${testName}.test.ts`);
2564
+ if (ctx?.modelService) {
2565
+ for (const scenario of session.bddScenarios) {
2566
+ const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
2567
+ const testPath = path5__namespace.join(testDir, `${testName}.test.ts`);
2568
+ const loader = new LoadingIndicator(`\u751F\u6210\u6D4B\u8BD5: ${scenario.feature.slice(0, 20)}...`);
2569
+ loader.start();
2570
+ try {
2571
+ const content = await generateTestFileWithAI(scenario, session, ctx);
2572
+ await fs5__namespace.writeFile(testPath, content, "utf-8");
2573
+ testFiles.push(`tests/${testName}.test.ts`);
2574
+ loader.stop(chalk9__default.default.green(` \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210`));
2575
+ } catch {
2576
+ const content = generateTestFile(scenario, session);
2577
+ await fs5__namespace.writeFile(testPath, content, "utf-8");
2578
+ testFiles.push(`tests/${testName}.test.ts`);
2579
+ loader.stop(chalk9__default.default.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840\u6D4B\u8BD5\u6A21\u677F"));
2580
+ }
2581
+ }
2582
+ } else {
2583
+ for (const scenario of session.bddScenarios) {
2584
+ const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
2585
+ const testPath = path5__namespace.join(testDir, `${testName}.test.ts`);
2586
+ const content = generateTestFile(scenario, session);
2587
+ await fs5__namespace.writeFile(testPath, content, "utf-8");
2588
+ testFiles.push(`tests/${testName}.test.ts`);
2589
+ }
2449
2590
  }
2450
2591
  return testFiles;
2451
2592
  }
2452
- function generateTestFile(scenario) {
2593
+ async function generateTestFileWithAI(scenario, session, ctx) {
2594
+ 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
2595
+
2596
+ ## \u529F\u80FD\u540D\u79F0
2597
+ ${scenario.feature}
2598
+
2599
+ ## BDD \u573A\u666F
2600
+ ${scenario.scenarios.map((s) => `
2601
+ ### ${s.name}
2602
+ - Given: ${s.given.join(", ")}
2603
+ - When: ${s.when.join(", ")}
2604
+ - Then: ${s.then.join(", ")}
2605
+ `).join("\n")}
2606
+
2607
+ ## \u9879\u76EE\u4E0A\u4E0B\u6587
2608
+ - \u6280\u672F\u6808: ${session.context?.techStack?.join(", ") || "TypeScript"}
2609
+ - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
2610
+
2611
+ ## \u8981\u6C42
2612
+ 1. \u4F7F\u7528 vitest \u6D4B\u8BD5\u6846\u67B6 (describe, it, expect, beforeEach \u7B49)
2613
+ 2. \u6BCF\u4E2A\u573A\u666F\u751F\u6210\u4E00\u4E2A\u72EC\u7ACB\u7684\u6D4B\u8BD5\u7528\u4F8B
2614
+ 3. \u6D4B\u8BD5\u4EE3\u7801\u8981\u5B8C\u6574\u53EF\u8FD0\u884C\uFF0C\u5305\u542B\u5FC5\u8981\u7684 mock \u548C setup
2615
+ 4. \u4F7F\u7528\u4E2D\u6587\u6CE8\u91CA\u8BF4\u660E\u6D4B\u8BD5\u610F\u56FE
2616
+ 5. \u6D4B\u8BD5\u8981\u8986\u76D6\u6B63\u5E38\u6D41\u7A0B\u548C\u8FB9\u754C\u60C5\u51B5
2617
+
2618
+ \u8BF7\u76F4\u63A5\u8F93\u51FA\u6D4B\u8BD5\u4EE3\u7801\uFF0C\u4E0D\u9700\u8981\u89E3\u91CA\u3002`;
2619
+ const response = await ctx.modelService.sendMessage([
2620
+ { role: "user", content: prompt2 }
2621
+ ], {
2622
+ temperature: 0.3,
2623
+ maxTokens: 4e3
2624
+ });
2625
+ const codeMatch = response.content.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
2626
+ if (codeMatch) {
2627
+ return codeMatch[1].trim();
2628
+ }
2629
+ return response.content;
2630
+ }
2631
+ function generateTestFile(scenario, session) {
2453
2632
  const lines = [];
2454
- lines.push(`import { describe, it, expect } from 'vitest';`);
2633
+ lines.push(`import { describe, it, expect, beforeEach } from 'vitest';`);
2455
2634
  lines.push("");
2635
+ lines.push(`/**`);
2636
+ lines.push(` * ${scenario.feature} \u529F\u80FD\u6D4B\u8BD5`);
2637
+ lines.push(` * `);
2638
+ lines.push(` * BDD \u573A\u666F\u6570\u91CF: ${scenario.scenarios.length}`);
2639
+ if (session?.context?.techStack) {
2640
+ lines.push(` * \u6280\u672F\u6808: ${session.context.techStack.join(", ")}`);
2641
+ }
2642
+ lines.push(` */`);
2456
2643
  lines.push(`describe('${scenario.feature}', () => {`);
2457
2644
  for (const s of scenario.scenarios) {
2458
- lines.push(` it('${s.name}', () => {`);
2459
- lines.push(` // Given: ${s.given.join(", ")}`);
2460
- lines.push(` // When: ${s.when.join(", ")}`);
2461
- lines.push(` // Then: ${s.then.join(", ")}`);
2462
- lines.push(` expect(true).toBe(true); // TODO: \u5B9E\u73B0\u6D4B\u8BD5`);
2463
- lines.push(` });`);
2464
2645
  lines.push("");
2646
+ lines.push(` /**`);
2647
+ lines.push(` * \u573A\u666F: ${s.name}`);
2648
+ lines.push(` * Given: ${s.given.join(", ")}`);
2649
+ lines.push(` * When: ${s.when.join(", ")}`);
2650
+ lines.push(` * Then: ${s.then.join(", ")}`);
2651
+ lines.push(` */`);
2652
+ lines.push(` it('${s.name}', async () => {`);
2653
+ lines.push(` // Arrange (Given)`);
2654
+ for (const g of s.given) {
2655
+ lines.push(` // ${g}`);
2656
+ }
2657
+ lines.push(` const input = {}; // TODO: \u8BBE\u7F6E\u521D\u59CB\u72B6\u6001`);
2658
+ lines.push("");
2659
+ lines.push(` // Act (When)`);
2660
+ for (const w of s.when) {
2661
+ lines.push(` // ${w}`);
2662
+ }
2663
+ lines.push(` const result = {}; // TODO: \u6267\u884C\u64CD\u4F5C`);
2664
+ lines.push("");
2665
+ lines.push(` // Assert (Then)`);
2666
+ for (const t of s.then) {
2667
+ lines.push(` // ${t}`);
2668
+ }
2669
+ lines.push(` expect(result).toBeDefined(); // TODO: \u5B8C\u5584\u65AD\u8A00`);
2670
+ lines.push(` });`);
2465
2671
  }
2672
+ lines.push("");
2466
2673
  lines.push(`});`);
2467
2674
  return lines.join("\n");
2468
2675
  }
@@ -2508,7 +2715,7 @@ function extractUrls(text) {
2508
2715
  const matches = text.match(urlRegex);
2509
2716
  return matches ? [...new Set(matches)] : [];
2510
2717
  }
2511
- async function fetchAndAnalyzeReference(url, ctx) {
2718
+ async function fetchAndAnalyzeReference(url, ctx, projectContext) {
2512
2719
  const type = detectResourceType(url);
2513
2720
  let content = "";
2514
2721
  let analysis = "";
@@ -2523,7 +2730,7 @@ async function fetchAndAnalyzeReference(url, ctx) {
2523
2730
  }
2524
2731
  content = await response.text();
2525
2732
  if (ctx.modelService.getCurrentModel()) {
2526
- analysis = await analyzeReferenceContent(url, content, type, ctx);
2733
+ analysis = await analyzeReferenceContent(url, content, type, ctx, projectContext);
2527
2734
  } else {
2528
2735
  analysis = extractBasicInfo(content, type);
2529
2736
  }
@@ -2544,40 +2751,72 @@ function detectResourceType(url) {
2544
2751
  }
2545
2752
  return "webpage";
2546
2753
  }
2547
- async function analyzeReferenceContent(url, content, type, ctx) {
2548
- const typePrompts = {
2549
- webpage: "\u5206\u6790\u8FD9\u4E2A\u7F51\u9875\u7684\u529F\u80FD\u3001UI\u7EC4\u4EF6\u548C\u4EA4\u4E92\u65B9\u5F0F",
2550
- design: "\u5206\u6790\u8FD9\u4E2A\u8BBE\u8BA1\u7A3F\u7684\u5E03\u5C40\u3001\u7EC4\u4EF6\u548C\u6837\u5F0F",
2551
- image: "\u63CF\u8FF0\u8FD9\u4E2A\u56FE\u7247\u7684\u5185\u5BB9\u548C\u8BBE\u8BA1\u5143\u7D20",
2552
- api: "\u5206\u6790\u8FD9\u4E2AAPI\u7684\u7ED3\u6784\u548C\u53C2\u6570"
2553
- };
2754
+ async function analyzeReferenceContent(url, content, type, ctx, projectContext) {
2554
2755
  const prompt2 = `
2555
- \u8BF7\u5206\u6790\u4EE5\u4E0B\u53C2\u8003\u8D44\u6E90\u7684 URL\uFF0C\u63D0\u53D6\u5BF9\u5F00\u53D1\u6709\u7528\u7684\u4FE1\u606F\uFF1A
2756
+ \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
2757
+
2758
+ ## \u91CD\u8981\u8BF4\u660E
2759
+ \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
2760
+ \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
2761
+
2762
+ ${projectContext ? `## \u5F53\u524D\u9879\u76EE\u89C4\u8303
2763
+ - \u9879\u76EE\u540D\u79F0: ${projectContext.name}
2764
+ - \u6280\u672F\u6808: ${projectContext.techStack.join(", ") || "\u672A\u6307\u5B9A"}
2765
+ - \u6846\u67B6: ${projectContext.framework || "\u672A\u6307\u5B9A"}
2766
+ - \u5F00\u53D1\u89C4\u8303\u6458\u8981: ${projectContext.devStandards?.slice(0, 1500) || "\u672A\u6307\u5B9A"}
2767
+ ` : ""}
2768
+
2769
+ ## \u53C2\u8003\u8D44\u6E90\u4FE1\u606F
2770
+ - URL: ${url}
2771
+ - \u7C7B\u578B: ${type}
2772
+
2773
+ ## \u7F51\u9875\u5185\u5BB9
2774
+ \`\`\`html
2775
+ ${content.slice(0, 8e3)}
2776
+ \`\`\`
2556
2777
 
2557
- URL: ${url}
2558
- \u7C7B\u578B: ${type}
2778
+ ## \u5206\u6790\u8981\u6C42
2559
2779
 
2560
- \u5185\u5BB9\u6458\u8981:
2561
- ${content.slice(0, 5e3)}
2780
+ \u8BF7\u6309\u7167\u4EE5\u4E0B\u7ED3\u6784\u8FDB\u884C\u5206\u6790\uFF1A
2562
2781
 
2563
- ${typePrompts[type]}
2782
+ ### 1. \u4E1A\u52A1\u529F\u80FD\u5206\u6790\uFF08\u91CD\u70B9\uFF09
2783
+ - \u6838\u5FC3\u4E1A\u52A1\u529F\u80FD\u662F\u4EC0\u4E48\uFF1F\uFF08\u8BE6\u7EC6\u63CF\u8FF0\u7528\u6237\u80FD\u505A\u4EC0\u4E48\uFF09
2784
+ - \u4E1A\u52A1\u6D41\u7A0B\u662F\u4EC0\u4E48\uFF1F\uFF08\u7528\u6237\u64CD\u4F5C\u6B65\u9AA4\uFF09
2785
+ - \u4E1A\u52A1\u89C4\u5219\u662F\u4EC0\u4E48\uFF1F\uFF08\u8F93\u5165\u9650\u5236\u3001\u8BA1\u7B97\u89C4\u5219\u7B49\uFF09
2564
2786
 
2565
- \u8BF7\u63D0\u53D6\uFF1A
2566
- 1. \u4E3B\u8981\u529F\u80FD\u70B9
2567
- 2. UI\u7EC4\u4EF6\u7ED3\u6784
2568
- 3. \u4EA4\u4E92\u65B9\u5F0F
2569
- 4. \u6570\u636E\u7ED3\u6784\uFF08\u5982\u679C\u6709\uFF09
2570
- 5. \u6280\u672F\u5B9E\u73B0\u5EFA\u8BAE
2787
+ ### 2. UI/UX \u7ED3\u6784\u5206\u6790
2788
+ - \u9875\u9762\u5E03\u5C40\u7ED3\u6784\uFF08\u4E0D\u6D89\u53CA\u5177\u4F53\u6280\u672F\u5B9E\u73B0\uFF09
2789
+ - \u4E3B\u8981\u7EC4\u4EF6/\u6A21\u5757\u6709\u54EA\u4E9B\uFF1F
2790
+ - \u4EA4\u4E92\u65B9\u5F0F\uFF08\u70B9\u51FB\u3001\u8F93\u5165\u3001\u62D6\u62FD\u7B49\uFF09
2571
2791
 
2572
- \u4EE5\u7B80\u6D01\u7684\u8981\u70B9\u5F62\u5F0F\u8F93\u51FA\u3002
2792
+ ### 3. \u6570\u636E\u6A21\u578B\u5206\u6790
2793
+ - \u9700\u8981\u54EA\u4E9B\u6570\u636E\u5B57\u6BB5\uFF1F
2794
+ - \u6570\u636E\u4E4B\u95F4\u7684\u5173\u7CFB
2795
+ - \u6570\u636E\u6765\u6E90\uFF08\u7528\u6237\u8F93\u5165/\u8BA1\u7B97/API\uFF09
2796
+
2797
+ ### 4. \u529F\u80FD\u62C6\u5206\u5EFA\u8BAE
2798
+ \u5C06\u529F\u80FD\u62C6\u5206\u4E3A\u53EF\u72EC\u7ACB\u5F00\u53D1\u7684\u4EFB\u52A1\uFF1A
2799
+ - \u4EFB\u52A1\u540D\u79F0\uFF08\u7B80\u77ED\u660E\u786E\uFF09
2800
+ - \u4EFB\u52A1\u63CF\u8FF0\uFF08\u4E1A\u52A1\u89D2\u5EA6\uFF09
2801
+ - \u9A8C\u6536\u6807\u51C6
2802
+
2803
+ \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
2804
+ \u6280\u672F\u5B9E\u73B0\u65B9\u6848\u7531\u9879\u76EE\u89C4\u8303\u51B3\u5B9A\uFF0C\u6B64\u5904\u4E0D\u6D89\u53CA\u3002
2573
2805
  `;
2806
+ const loader = new LoadingIndicator("AI \u6B63\u5728\u5206\u6790\u53C2\u8003\u8D44\u6E90");
2807
+ loader.start();
2574
2808
  try {
2575
2809
  const response = await ctx.modelService.sendMessage([
2576
2810
  { role: "user", content: prompt2 }
2577
- ], { temperature: 0.3, maxTokens: 2e3 });
2811
+ ], {
2812
+ temperature: 0.3,
2813
+ maxTokens: 4e3
2814
+ });
2815
+ loader.stop(chalk9__default.default.green(" \u2713 \u5206\u6790\u5B8C\u6210"));
2578
2816
  return response.content;
2579
- } catch {
2580
- return extractBasicInfo(content, type);
2817
+ } catch (error) {
2818
+ loader.stop();
2819
+ throw error;
2581
2820
  }
2582
2821
  }
2583
2822
  function extractBasicInfo(content, type) {
@@ -2618,10 +2857,43 @@ function getActiveSession() {
2618
2857
  function clearActiveSession() {
2619
2858
  activeSession = null;
2620
2859
  }
2621
- var MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
2860
+ var LoadingIndicator, MAX_FILE_SIZE2, COMPLEXITY_THRESHOLD, CLARITY_THRESHOLD, activeSession, new_default;
2622
2861
  var init_new = __esm({
2623
2862
  "src/commands/new.ts"() {
2624
2863
  init_cjs_shims();
2864
+ LoadingIndicator = class {
2865
+ frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2866
+ frameIndex = 0;
2867
+ interval = null;
2868
+ message;
2869
+ constructor(message) {
2870
+ this.message = message;
2871
+ }
2872
+ start() {
2873
+ process.stdout.write("\x1B[?25l");
2874
+ this.interval = setInterval(() => {
2875
+ const frame = this.frames[this.frameIndex];
2876
+ process.stdout.write(`\r${chalk9__default.default.cyan(frame)} ${this.message}...`);
2877
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
2878
+ }, 80);
2879
+ }
2880
+ update(message) {
2881
+ this.message = message;
2882
+ }
2883
+ stop(finalMessage) {
2884
+ if (this.interval) {
2885
+ clearInterval(this.interval);
2886
+ this.interval = null;
2887
+ }
2888
+ process.stdout.write("\x1B[?25h");
2889
+ if (finalMessage) {
2890
+ process.stdout.write(`\r${finalMessage}
2891
+ `);
2892
+ } else {
2893
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
2894
+ }
2895
+ }
2896
+ };
2625
2897
  MAX_FILE_SIZE2 = 1024 * 1024;
2626
2898
  COMPLEXITY_THRESHOLD = 6;
2627
2899
  CLARITY_THRESHOLD = 0.6;