qat-cli 0.3.3 → 0.3.4

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.d.cts CHANGED
@@ -736,16 +736,21 @@ declare class OpenAICompatibleProvider implements AIProvider {
736
736
  latencyMs?: number;
737
737
  }>;
738
738
  private chat;
739
+ /**
740
+ * 读取 SSE 流式响应,实时输出内容
741
+ */
742
+ private readStream;
743
+ /**
744
+ * 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
745
+ * 目标:在保留准确性的前提下减少 token 消耗
746
+ */
747
+ private compressSourceCode;
739
748
  private buildGenerateTestSystemPrompt;
740
749
  private buildGenerateTestUserPrompt;
741
750
  /**
742
751
  * 根据测试类型和源文件路径,计算从测试文件到源文件的正确相对导入路径
743
752
  */
744
753
  private computeTestImportPath;
745
- /**
746
- * 获取测试输出目录
747
- */
748
- private getTestOutputDir;
749
754
  private parseGenerateTestResponse;
750
755
  private buildReviewTestSystemPrompt;
751
756
  private buildReviewTestUserPrompt;
package/dist/index.d.ts CHANGED
@@ -736,16 +736,21 @@ declare class OpenAICompatibleProvider implements AIProvider {
736
736
  latencyMs?: number;
737
737
  }>;
738
738
  private chat;
739
+ /**
740
+ * 读取 SSE 流式响应,实时输出内容
741
+ */
742
+ private readStream;
743
+ /**
744
+ * 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
745
+ * 目标:在保留准确性的前提下减少 token 消耗
746
+ */
747
+ private compressSourceCode;
739
748
  private buildGenerateTestSystemPrompt;
740
749
  private buildGenerateTestUserPrompt;
741
750
  /**
742
751
  * 根据测试类型和源文件路径,计算从测试文件到源文件的正确相对导入路径
743
752
  */
744
753
  private computeTestImportPath;
745
- /**
746
- * 获取测试输出目录
747
- */
748
- private getTestOutputDir;
749
754
  private parseGenerateTestResponse;
750
755
  private buildReviewTestSystemPrompt;
751
756
  private buildReviewTestUserPrompt;
package/dist/index.js CHANGED
@@ -2524,6 +2524,15 @@ var NoopAIProvider = class {
2524
2524
  };
2525
2525
 
2526
2526
  // src/ai/openai-provider.ts
2527
+ import chalk2 from "chalk";
2528
+ function isDebug() {
2529
+ return process.env.QAT_DEBUG === "true";
2530
+ }
2531
+ function debugLog(tag, ...args) {
2532
+ if (!isDebug()) return;
2533
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
2534
+ console.error(chalk2.gray(`[DEBUG ${timestamp}] [${tag}]`), ...args);
2535
+ }
2527
2536
  var OpenAICompatibleProvider = class {
2528
2537
  constructor(config) {
2529
2538
  this.capabilities = {
@@ -2537,25 +2546,20 @@ var OpenAICompatibleProvider = class {
2537
2546
  this.baseUrl = config.baseUrl || this.getDefaultBaseUrl(config.provider);
2538
2547
  }
2539
2548
  async generateTest(req) {
2549
+ debugLog("GENERATE", `type=${req.type} target=${req.target}`);
2540
2550
  const systemPrompt = this.buildGenerateTestSystemPrompt(req);
2541
2551
  const userPrompt = this.buildGenerateTestUserPrompt(req);
2542
2552
  const content = await this.chat(systemPrompt, userPrompt);
2543
- return this.parseGenerateTestResponse(content);
2553
+ const result = this.parseGenerateTestResponse(content);
2554
+ debugLog("GENERATE", `done code=${result.code.length}chars confidence=${result.confidence}`);
2555
+ return result;
2544
2556
  }
2545
2557
  async analyzeResult(req) {
2546
- const systemPrompt = `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u6D4B\u8BD5\u5206\u6790\u4E13\u5BB6\u3002\u5206\u6790\u6D4B\u8BD5\u8FD0\u884C\u7ED3\u679C\uFF0C\u627E\u51FA\u95EE\u9898\u6839\u56E0\uFF0C\u7ED9\u51FA\u5177\u4F53\u53EF\u64CD\u4F5C\u7684\u6539\u8FDB\u5EFA\u8BAE\u3002
2547
- \u8F93\u51FA\u683C\u5F0F\uFF1A
2548
- 1. \u5206\u6790\u6458\u8981\uFF081-3\u53E5\u8BDD\uFF09
2549
- 2. \u6539\u8FDB\u5EFA\u8BAE\u5217\u8868\uFF08\u6BCF\u6761\u5EFA\u8BAE\u5177\u4F53\u3001\u53EF\u64CD\u4F5C\uFF09`;
2550
- const resultSummary = req.testResults.map((r) => {
2551
- const failed = r.suites.flatMap((s) => s.tests.filter((t) => t.status === "failed"));
2552
- return `\u7C7B\u578B: ${r.type}, \u72B6\u6001: ${r.status}, \u8017\u65F6: ${r.duration}ms, \u5931\u8D25\u7528\u4F8B: ${failed.length}`;
2553
- }).join("\n");
2554
- const errorDetails = req.errorLogs?.join("\n") || req.testResults.flatMap((r) => r.suites.flatMap((s) => s.tests.filter((t) => t.status === "failed" && t.error))).map((t) => `[${t.name}] ${t.error?.message}`).join("\n") || "\u65E0\u9519\u8BEF\u8BE6\u60C5";
2555
- const userPrompt = `\u6D4B\u8BD5\u7ED3\u679C:
2556
- ${resultSummary}
2557
-
2558
- \u9519\u8BEF\u8BE6\u60C5:
2558
+ const systemPrompt = `\u6D4B\u8BD5\u5206\u6790\u4E13\u5BB6\u3002\u627E\u95EE\u9898\u6839\u56E0\uFF0C\u7ED9\u53EF\u64CD\u4F5C\u5EFA\u8BAE\u3002
2559
+ \u8F93\u51FA:1.\u6458\u8981(1-3\u53E5) 2.\u5EFA\u8BAE\u5217\u8868`;
2560
+ const failed = req.testResults.flatMap((r) => r.suites.flatMap((s) => s.tests.filter((t) => t.status === "failed")));
2561
+ const errorDetails = req.errorLogs?.join("\n") || failed.map((t) => `[${t.name}] ${t.error?.message}`).join("\n") || "\u65E0";
2562
+ const userPrompt = `\u5931\u8D25:${failed.length}
2559
2563
  ${errorDetails}`;
2560
2564
  const content = await this.chat(systemPrompt, userPrompt);
2561
2565
  return {
@@ -2565,23 +2569,22 @@ ${errorDetails}`;
2565
2569
  };
2566
2570
  }
2567
2571
  async suggestFix(error) {
2568
- const systemPrompt = `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u4EE3\u7801\u4FEE\u590D\u4E13\u5BB6\u3002\u6839\u636E\u6D4B\u8BD5\u9519\u8BEF\u4FE1\u606F\uFF0C\u7ED9\u51FA\u5177\u4F53\u7684\u4FEE\u590D\u5EFA\u8BAE\u3002
2569
- \u6BCF\u6761\u5EFA\u8BAE\u5E94\u8BE5\u5305\u542B\uFF1A
2570
- 1. \u95EE\u9898\u5B9A\u4F4D
2571
- 2. \u4FEE\u590D\u65B9\u6848
2572
- 3. \u793A\u4F8B\u4EE3\u7801\uFF08\u5982\u679C\u9002\u7528\uFF09`;
2573
- const userPrompt = `\u9519\u8BEF\u4FE1\u606F: ${error.message}
2574
- ${error.stack ? `\u5806\u6808: ${error.stack}` : ""}
2575
- ${error.expected ? `\u671F\u671B\u503C: ${error.expected}` : ""}
2576
- ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
2572
+ const systemPrompt = `\u4EE3\u7801\u4FEE\u590D\u4E13\u5BB6\u3002\u7ED9\u51FA:1.\u95EE\u9898\u5B9A\u4F4D 2.\u4FEE\u590D\u65B9\u6848 3.\u793A\u4F8B\u4EE3\u7801`;
2573
+ const userPrompt = `\u9519\u8BEF:${error.message}${error.stack ? `
2574
+ \u5806\u6808:${error.stack}` : ""}${error.expected ? `
2575
+ \u671F\u671B:${error.expected}` : ""}${error.actual ? `
2576
+ \u5B9E\u9645:${error.actual}` : ""}`;
2577
2577
  const content = await this.chat(systemPrompt, userPrompt);
2578
2578
  return content.split("\n").filter((l) => l.trim().startsWith("-") || l.trim().startsWith("\u2022") || l.trim().match(/^\d+\./)).map((l) => l.replace(/^[-•\d.]+\s*/, "").trim()).filter(Boolean);
2579
2579
  }
2580
2580
  async reviewTest(req) {
2581
+ debugLog("REVIEW", `target=${req.target} type=${req.testType}`);
2581
2582
  const systemPrompt = this.buildReviewTestSystemPrompt(req);
2582
2583
  const userPrompt = this.buildReviewTestUserPrompt(req);
2583
2584
  const content = await this.chat(systemPrompt, userPrompt);
2584
- return this.parseReviewTestResponse(content);
2585
+ const result = this.parseReviewTestResponse(content);
2586
+ debugLog("REVIEW", `approved=${result.approved} score=${result.score} feedback=${result.feedback}`);
2587
+ return result;
2585
2588
  }
2586
2589
  // ─── 内部方法 ──────────────────────────────────────────────
2587
2590
  /**
@@ -2645,8 +2648,9 @@ ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
2645
2648
  return { ok: false, message: `\u8FDE\u63A5\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`, latencyMs };
2646
2649
  }
2647
2650
  }
2648
- async chat(systemPrompt, userPrompt) {
2651
+ async chat(systemPrompt, userPrompt, retries = 2) {
2649
2652
  const url = `${this.baseUrl}/chat/completions`;
2653
+ const useStream = isDebug();
2650
2654
  const body = {
2651
2655
  model: this.model,
2652
2656
  messages: [
@@ -2656,101 +2660,189 @@ ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
2656
2660
  temperature: 0.3,
2657
2661
  max_tokens: 4096
2658
2662
  };
2663
+ if (useStream) {
2664
+ body.stream = true;
2665
+ }
2659
2666
  const headers = {
2660
2667
  "Content-Type": "application/json"
2661
2668
  };
2662
2669
  if (this.apiKey) {
2663
2670
  headers["Authorization"] = `Bearer ${this.apiKey}`;
2664
2671
  }
2665
- const response = await fetch(url, {
2666
- method: "POST",
2667
- headers,
2668
- body: JSON.stringify(body),
2669
- signal: AbortSignal.timeout(6e4)
2670
- // 60s timeout
2671
- });
2672
- if (!response.ok) {
2673
- const text = await response.text().catch(() => "");
2674
- throw new Error(`AI API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${text.slice(0, 500)}`);
2672
+ debugLog("REQUEST", `POST ${url}`);
2673
+ debugLog("REQUEST", `model=${this.model} stream=${useStream}`);
2674
+ debugLog("SYSTEM", systemPrompt.length > 500 ? `${systemPrompt.slice(0, 500)}...` : systemPrompt);
2675
+ debugLog("USER", userPrompt.length > 1e3 ? `${userPrompt.slice(0, 1e3)}...` : userPrompt);
2676
+ let lastError = null;
2677
+ for (let attempt = 0; attempt <= retries; attempt++) {
2678
+ try {
2679
+ if (attempt > 0) {
2680
+ debugLog("RETRY", `\u7B2C${attempt}\u6B21\u91CD\u8BD5...`);
2681
+ }
2682
+ const response = await fetch(url, {
2683
+ method: "POST",
2684
+ headers,
2685
+ body: JSON.stringify(body),
2686
+ signal: AbortSignal.timeout(12e4)
2687
+ // 120s timeout
2688
+ });
2689
+ if (!response.ok) {
2690
+ const text = await response.text().catch(() => "");
2691
+ if ((response.status === 429 || response.status >= 500) && attempt < retries) {
2692
+ const delay = Math.min(1e3 * Math.pow(2, attempt), 8e3);
2693
+ debugLog("RETRY", `HTTP ${response.status}, ${delay}ms\u540E\u91CD\u8BD5`);
2694
+ await new Promise((r) => setTimeout(r, delay));
2695
+ lastError = new Error(`AI API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${text.slice(0, 200)}`);
2696
+ continue;
2697
+ }
2698
+ throw new Error(`AI API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${text.slice(0, 500)}`);
2699
+ }
2700
+ if (useStream && response.body) {
2701
+ const content = await this.readStream(response.body);
2702
+ debugLog("RESPONSE", content.length > 500 ? `${content.slice(0, 500)}...` : content);
2703
+ return content;
2704
+ }
2705
+ const data = await response.json();
2706
+ if (!data.choices?.[0]?.message?.content) {
2707
+ throw new Error("AI API \u8FD4\u56DE\u7A7A\u54CD\u5E94");
2708
+ }
2709
+ debugLog("RESPONSE", `tokens: prompt=${data.usage?.prompt_tokens} completion=${data.usage?.completion_tokens} total=${data.usage?.total_tokens}`);
2710
+ debugLog("RESPONSE", data.choices[0].message.content.length > 500 ? `${data.choices[0].message.content.slice(0, 500)}...` : data.choices[0].message.content);
2711
+ return data.choices[0].message.content;
2712
+ } catch (error) {
2713
+ if (error instanceof Error && error.name === "TimeoutError" && attempt < retries) {
2714
+ debugLog("TIMEOUT", `\u7B2C${attempt}\u6B21\u8D85\u65F6\uFF0C\u91CD\u8BD5\u4E2D...`);
2715
+ lastError = error;
2716
+ continue;
2717
+ }
2718
+ throw error;
2719
+ }
2675
2720
  }
2676
- const data = await response.json();
2677
- if (!data.choices?.[0]?.message?.content) {
2678
- throw new Error("AI API \u8FD4\u56DE\u7A7A\u54CD\u5E94");
2721
+ throw lastError || new Error("AI API \u8BF7\u6C42\u5931\u8D25");
2722
+ }
2723
+ /**
2724
+ * 读取 SSE 流式响应,实时输出内容
2725
+ */
2726
+ async readStream(body) {
2727
+ const chunks = [];
2728
+ const decoder = new TextDecoder();
2729
+ const reader = body.getReader();
2730
+ let buffer = "";
2731
+ let lineCount = 0;
2732
+ try {
2733
+ while (true) {
2734
+ const { done, value } = await reader.read();
2735
+ if (done) break;
2736
+ buffer += decoder.decode(value, { stream: true });
2737
+ const lines = buffer.split("\n");
2738
+ buffer = lines.pop() || "";
2739
+ for (const line of lines) {
2740
+ const trimmed = line.trim();
2741
+ if (!trimmed || trimmed === "data: [DONE]") continue;
2742
+ if (!trimmed.startsWith("data: ")) continue;
2743
+ try {
2744
+ const json = JSON.parse(trimmed.slice(6));
2745
+ const delta = json.choices?.[0]?.delta?.content;
2746
+ if (delta) {
2747
+ chunks.push(delta);
2748
+ lineCount++;
2749
+ process.stderr.write(chalk2.gray(delta));
2750
+ if (lineCount % 20 === 0) {
2751
+ process.stderr.write("\n");
2752
+ }
2753
+ }
2754
+ if (json.choices?.[0]?.finish_reason === "stop") {
2755
+ debugLog("STREAM", "\u5B8C\u6210");
2756
+ }
2757
+ } catch {
2758
+ }
2759
+ }
2760
+ }
2761
+ } finally {
2762
+ reader.releaseLock();
2679
2763
  }
2680
- return data.choices[0].message.content;
2764
+ if (chunks.length > 0) {
2765
+ process.stderr.write("\n");
2766
+ }
2767
+ return chunks.join("");
2768
+ }
2769
+ /**
2770
+ * 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
2771
+ * 目标:在保留准确性的前提下减少 token 消耗
2772
+ */
2773
+ compressSourceCode(code, maxLength = 3e3) {
2774
+ let compressed = code;
2775
+ compressed = compressed.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
2776
+ compressed = compressed.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi, (_match, content) => {
2777
+ return `<template>${content.replace(/\s*(?:class|style)\s*=\s*["'][^"']*["']/gi, "")}</template>`;
2778
+ });
2779
+ compressed = compressed.replace(/\/\*[\s\S]*?\*\//g, "");
2780
+ compressed = compressed.replace(/(^|[^:])(\/\/.*$)/gm, "$1");
2781
+ compressed = compressed.replace(/\n\s*\n\s*\n/g, "\n\n");
2782
+ compressed = compressed.split("\n").map((line) => line.trimEnd()).join("\n").trim();
2783
+ if (compressed.length > maxLength) {
2784
+ const scriptMatch = compressed.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
2785
+ const templateMatch = compressed.match(/<template[^>]*>([\s\S]*?)<\/template>/i);
2786
+ if (scriptMatch) {
2787
+ let scriptPart = scriptMatch[1].trim();
2788
+ scriptPart = compressFunctionBodies(scriptPart, maxLength * 0.7);
2789
+ const templatePart = templateMatch ? `<template>${templateMatch[1].replace(/\s+/g, " ").trim().slice(0, 300)}...</template>` : "";
2790
+ compressed = `${templatePart}
2791
+ <script${scriptMatch[0].match(/<script[^>]*>/)?.[0]?.slice(7) || ">"}>${scriptPart}</script>`;
2792
+ } else {
2793
+ compressed = compressFunctionBodies(compressed, maxLength);
2794
+ }
2795
+ }
2796
+ return compressed;
2681
2797
  }
2682
2798
  buildGenerateTestSystemPrompt(req) {
2683
2799
  const typeMap = {
2684
- unit: "\u5355\u5143\u6D4B\u8BD5\uFF08Vitest + @vue/test-utils\uFF09",
2685
- component: "\u7EC4\u4EF6\u6D4B\u8BD5\uFF08Vitest + @vue/test-utils + mount\uFF09",
2686
- e2e: "E2E\u7AEF\u5230\u7AEF\u6D4B\u8BD5\uFF08Playwright\uFF09",
2687
- api: "API\u63A5\u53E3\u6D4B\u8BD5\uFF08Vitest + fetch\uFF09",
2688
- visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5\uFF08Playwright screenshot\uFF09",
2689
- performance: "\u6027\u80FD\u6D4B\u8BD5\uFF08Playwright + performance metrics\uFF09"
2800
+ unit: "\u5355\u5143\u6D4B\u8BD5(Vitest)",
2801
+ component: "\u7EC4\u4EF6\u6D4B\u8BD5(Vitest+@vue/test-utils)",
2802
+ e2e: "E2E\u6D4B\u8BD5(Playwright)",
2803
+ api: "API\u6D4B\u8BD5(Vitest+fetch)",
2804
+ visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5(Playwright)",
2805
+ performance: "\u6027\u80FD\u6D4B\u8BD5(Playwright)"
2690
2806
  };
2691
- return `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u524D\u7AEF\u6D4B\u8BD5\u5DE5\u7A0B\u5E08\uFF0C\u64C5\u957F\u7F16\u5199\u9AD8\u8D28\u91CF\u7684${typeMap[req.type] || req.type}\u3002
2692
- \u8981\u6C42\uFF1A
2693
- 1. \u53EA\u8F93\u51FA\u6D4B\u8BD5\u4EE3\u7801\uFF0C\u4E0D\u8981\u591A\u4F59\u7684\u89E3\u91CA
2694
- 2. \u4EE3\u7801\u5FC5\u987B\u53EF\u76F4\u63A5\u8FD0\u884C\uFF0C\u5305\u542B\u6240\u6709\u5FC5\u8981\u7684 import
2695
- 3. \u6D4B\u8BD5\u7528\u4F8B\u8986\u76D6\uFF1A\u6B63\u5E38\u8DEF\u5F84\u3001\u8FB9\u754C\u6761\u4EF6\u3001\u9519\u8BEF\u5904\u7406
2696
- 4. \u4F7F\u7528\u4E2D\u6587\u63CF\u8FF0 it/test \u5757\u540D\u79F0
2697
- 5. Vue \u7EC4\u4EF6\u6D4B\u8BD5\u4F7F\u7528 @vue/test-utils \u7684 mount
2698
- 6. \u5982\u679C\u6709 props/emits \u4FE1\u606F\uFF0C\u52A1\u5FC5\u9488\u5BF9\u6BCF\u4E2A prop \u548C emit \u751F\u6210\u6D4B\u8BD5`;
2807
+ return `\u4F60\u662F\u524D\u7AEF\u6D4B\u8BD5\u5DE5\u7A0B\u5E08\uFF0C\u7F16\u5199${typeMap[req.type] || req.type}\u3002
2808
+ \u89C4\u5219:1.\u53EA\u8F93\u51FA\u6D4B\u8BD5\u4EE3\u7801 2.\u5305\u542B\u6240\u6709import 3.\u8986\u76D6\u6B63\u5E38/\u8FB9\u754C/\u9519\u8BEF 4.it\u540D\u7528\u4E2D\u6587 5.Vue\u7528mount 6.\u5FC5\u6D4B\u6BCF\u4E2Aprop\u548Cemit`;
2699
2809
  }
2700
2810
  buildGenerateTestUserPrompt(req) {
2701
2811
  const importPath = this.computeTestImportPath(req.type, req.target);
2702
- let prompt = `\u8BF7\u4E3A\u4EE5\u4E0B\u6587\u4EF6\u751F\u6210${req.type}\u6D4B\u8BD5\u4EE3\u7801:
2703
- \u76EE\u6807\u6587\u4EF6: ${req.target}
2704
- \u6D4B\u8BD5\u6587\u4EF6\u5C06\u653E\u5728: ${this.getTestOutputDir(req.type)}/
2705
- \u6B63\u786E\u7684 import \u8DEF\u5F84: ${importPath}
2706
-
2707
- \u91CD\u8981\uFF1Aimport \u8BED\u53E5\u4E2D\u5FC5\u987B\u4F7F\u7528\u4E0A\u8FF0\u6B63\u786E\u7684\u76F8\u5BF9\u8DEF\u5F84 ${importPath}\uFF0C\u4E0D\u8981\u4F7F\u7528 ${req.target} \u6216\u5176\u4ED6\u8DEF\u5F84\uFF01
2812
+ let prompt = `\u4E3A${req.target}\u751F\u6210${req.type}\u6D4B\u8BD5\u3002import\u8DEF\u5F84:${importPath}
2708
2813
  `;
2709
2814
  if (req.analysis) {
2710
- prompt += "\n\u6E90\u7801\u5206\u6790\u7ED3\u679C:\n";
2815
+ const parts = [];
2711
2816
  if (req.analysis.exports?.length > 0) {
2712
- prompt += `\u5BFC\u51FA\u9879:
2713
- ${req.analysis.exports.map((e) => {
2714
- const params = e.params?.length ? `(${e.params.join(", ")})` : "";
2715
- const asyncFlag = e.isAsync ? "async " : "";
2716
- return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;
2717
- }).join("\n")}
2718
- `;
2817
+ parts.push(`\u5BFC\u51FA:${req.analysis.exports.map((e) => {
2818
+ const p = e.params?.length ? `(${e.params.join(",")})` : "";
2819
+ return `${e.isAsync ? "async " : ""}${e.name}${p}[${e.kind}]`;
2820
+ }).join(",")}`);
2719
2821
  }
2720
2822
  if (req.analysis.props?.length) {
2721
- prompt += `Props:
2722
- ${req.analysis.props.map(
2723
- (p) => ` - ${p.name}: ${p.type}${p.required ? " (\u5FC5\u586B)" : " (\u53EF\u9009)"}`
2724
- ).join("\n")}
2725
- `;
2823
+ parts.push(`Props:${req.analysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",")}`);
2726
2824
  }
2727
2825
  if (req.analysis.emits?.length) {
2728
- prompt += `Emits:
2729
- ${req.analysis.emits.map(
2730
- (e) => ` - ${e.name}${e.params?.length ? `(${e.params.join(", ")})` : ""}`
2731
- ).join("\n")}
2732
- `;
2826
+ parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
2733
2827
  }
2734
2828
  if (req.analysis.methods?.length) {
2735
- prompt += `Methods: ${req.analysis.methods.join(", ")}
2736
- `;
2829
+ parts.push(`Methods:${req.analysis.methods.join(",")}`);
2737
2830
  }
2738
2831
  if (req.analysis.computed?.length) {
2739
- prompt += `Computed: ${req.analysis.computed.join(", ")}
2740
- `;
2832
+ parts.push(`Computed:${req.analysis.computed.join(",")}`);
2741
2833
  }
2834
+ prompt += parts.join("\n") + "\n";
2742
2835
  }
2743
2836
  if (req.context) {
2744
2837
  prompt += `
2745
- \u6E90\u7801\u5185\u5BB9:
2746
- \`\`\`typescript
2747
- ${req.context}
2838
+ \u6E90\u7801:
2839
+ \`\`\`
2840
+ ${this.compressSourceCode(req.context)}
2748
2841
  \`\`\`
2749
2842
  `;
2750
2843
  }
2751
2844
  if (req.framework) {
2752
- prompt += `
2753
- \u6846\u67B6: ${req.framework}`;
2845
+ prompt += `\u6846\u67B6:${req.framework}`;
2754
2846
  }
2755
2847
  return prompt;
2756
2848
  }
@@ -2780,20 +2872,6 @@ ${req.context}
2780
2872
  }
2781
2873
  return `${prefix}${cleanPath}`;
2782
2874
  }
2783
- /**
2784
- * 获取测试输出目录
2785
- */
2786
- getTestOutputDir(testType) {
2787
- const dirMap = {
2788
- unit: "tests/unit",
2789
- component: "tests/component",
2790
- e2e: "tests/e2e",
2791
- api: "tests/api",
2792
- visual: "tests/visual",
2793
- performance: "tests/e2e"
2794
- };
2795
- return dirMap[testType] || "tests/unit";
2796
- }
2797
2875
  parseGenerateTestResponse(content) {
2798
2876
  const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\s*\n([\s\S]*?)```/);
2799
2877
  const code = codeBlockMatch ? codeBlockMatch[1].trim() : content.replace(/^(?:```[\s\S]*?\n)?/, "").replace(/\n?```$/, "").trim();
@@ -2805,69 +2883,49 @@ ${req.context}
2805
2883
  };
2806
2884
  }
2807
2885
  buildReviewTestSystemPrompt(_req) {
2808
- return `\u4F60\u662F\u4E00\u4F4D\u4E25\u8C28\u7684\u6D4B\u8BD5\u5BA1\u8BA1\u4E13\u5BB6\u3002\u4F60\u7684\u804C\u8D23\u662F\u5BA1\u67E5 AI \u751F\u6210\u7684\u6D4B\u8BD5\u7528\u4F8B\u662F\u5426\u4E0E\u6E90\u7801\u8D34\u5207\u4E14\u51C6\u786E\u3002
2809
-
2810
- \u5BA1\u67E5\u6807\u51C6\uFF1A
2811
- 1. **\u6D4B\u8BD5\u7C7B\u578B\u5339\u914D**\uFF1A\u6D4B\u8BD5\u7C7B\u578B\u662F\u5426\u4E0E\u6E90\u7801\u6587\u4EF6\u6027\u8D28\u5339\u914D\uFF08\u5982 .vue \u5E94\u4E3A\u7EC4\u4EF6\u6D4B\u8BD5\uFF0Cutils \u5E94\u4E3A\u5355\u5143\u6D4B\u8BD5\uFF09
2812
- 2. **\u8986\u76D6\u5B8C\u6574\u6027**\uFF1A\u662F\u5426\u8986\u76D6\u4E86\u6E90\u7801\u4E2D\u7684\u6838\u5FC3\u5BFC\u51FA\u9879\uFF08\u51FD\u6570\u3001\u7EC4\u4EF6\u7684 props/emits/methods\uFF09
2813
- 3. **\u65AD\u8A00\u6709\u6548\u6027**\uFF1A\u65AD\u8A00\u662F\u5426\u771F\u5B9E\u68C0\u9A8C\u4E86\u88AB\u6D4B\u884C\u4E3A\uFF0C\u800C\u975E\u7A7A\u65AD\u8A00\u6216\u6C38\u771F\u65AD\u8A00
2814
- 4. **\u5BFC\u5165\u6B63\u786E\u6027**\uFF1Aimport \u8DEF\u5F84\u548C\u6A21\u5757\u662F\u5426\u6B63\u786E
2815
- 5. **\u4EE3\u7801\u53EF\u8FD0\u884C\u6027**\uFF1A\u6D4B\u8BD5\u4EE3\u7801\u662F\u5426\u53EF\u76F4\u63A5\u8FD0\u884C\uFF0C\u65E0\u8BED\u6CD5\u9519\u8BEF
2816
-
2817
- \u8F93\u51FA\u683C\u5F0F\uFF08\u4E25\u683C\u9075\u5B88\uFF09\uFF1A
2818
- APPROVED: true \u6216 false
2819
- SCORE: 0.0 \u5230 1.0 \u4E4B\u95F4\u7684\u8BC4\u5206
2820
- FEEDBACK: \u4E00\u53E5\u8BDD\u5BA1\u8BA1\u610F\u89C1
2821
- ISSUES: \u95EE\u9898\u5217\u8868\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF0C\u683C\u5F0F "- \u95EE\u9898\u63CF\u8FF0"\uFF09
2822
- SUGGESTIONS: \u6539\u8FDB\u5EFA\u8BAE\u5217\u8868\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF0C\u683C\u5F0F "- \u5EFA\u8BAE\u63CF\u8FF0"\uFF09`;
2886
+ return `\u4F60\u662F\u6D4B\u8BD5\u5BA1\u8BA1\u4E13\u5BB6\u3002\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u662F\u5426\u51C6\u786E\u3002
2887
+ \u6807\u51C6:1.\u7C7B\u578B\u5339\u914D 2.\u8986\u76D6\u6838\u5FC3\u5BFC\u51FA/props/emits 3.\u65AD\u8A00\u6709\u6548(\u975E\u6C38\u771F) 4.import\u6B63\u786E 5.\u53EF\u8FD0\u884C
2888
+ \u8F93\u51FA:
2889
+ APPROVED:true\u6216false
2890
+ SCORE:0.0-1.0
2891
+ FEEDBACK:\u4E00\u53E5\u8BDD
2892
+ ISSUES:- \u95EE\u9898(\u6BCF\u884C\u4E00\u4E2A)
2893
+ SUGGESTIONS:- \u5EFA\u8BAE(\u6BCF\u884C\u4E00\u4E2A)`;
2823
2894
  }
2824
2895
  buildReviewTestUserPrompt(req) {
2825
2896
  const importPath = this.computeTestImportPath(req.testType, req.target);
2826
- let prompt = `\u8BF7\u5BA1\u67E5\u4EE5\u4E0B\u6D4B\u8BD5\u7528\u4F8B\u662F\u5426\u4E0E\u6E90\u7801\u8D34\u5207\u4E14\u51C6\u786E\u3002
2827
-
2828
- \u88AB\u6D4B\u6587\u4EF6: ${req.target}
2829
- \u6D4B\u8BD5\u7C7B\u578B: ${req.testType}
2830
- \u6B63\u786E\u7684 import \u8DEF\u5F84\u5E94\u4E3A: ${importPath}\uFF08\u6D4B\u8BD5\u6587\u4EF6\u4F4D\u4E8E ${this.getTestOutputDir(req.testType)}/\uFF09
2831
-
2832
- --- \u6E90\u7801\u5185\u5BB9 ---
2833
- \`\`\`typescript
2834
- ${req.sourceCode}
2897
+ let prompt = `\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u3002\u88AB\u6D4B:${req.target} \u7C7B\u578B:${req.testType} import\u8DEF\u5F84:${importPath}
2898
+ `;
2899
+ prompt += `
2900
+ \u6E90\u7801:
2901
+ \`\`\`
2902
+ ${this.compressSourceCode(req.sourceCode, 2e3)}
2903
+ \`\`\`
2904
+ `;
2905
+ prompt += `
2906
+ \u6D4B\u8BD5\u4EE3\u7801:
2835
2907
  \`\`\`
2836
-
2837
- --- \u751F\u6210\u7684\u6D4B\u8BD5\u4EE3\u7801 ---
2838
- \`\`\`typescript
2839
2908
  ${req.testCode}
2840
2909
  \`\`\``;
2841
2910
  if (req.analysis) {
2842
- prompt += "\n\n--- \u6E90\u7801\u5206\u6790 ---";
2911
+ const parts = [];
2843
2912
  if (req.analysis.exports?.length > 0) {
2844
- prompt += `
2845
- \u5BFC\u51FA\u9879:
2846
- ${req.analysis.exports.map((e) => {
2847
- const params = e.params?.length ? `(${e.params.join(", ")})` : "";
2848
- const asyncFlag = e.isAsync ? "async " : "";
2849
- return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;
2850
- }).join("\n")}`;
2913
+ parts.push(`\u5BFC\u51FA:${req.analysis.exports.map((e) => `${e.isAsync ? "async " : ""}${e.name}(${e.params?.join(",") || ""})[${e.kind}]`).join(",")}`);
2851
2914
  }
2852
2915
  if (req.analysis.props?.length) {
2853
- prompt += `
2854
- Props:
2855
- ${req.analysis.props.map(
2856
- (p) => ` - ${p.name}: ${p.type}${p.required ? " (\u5FC5\u586B)" : " (\u53EF\u9009)"}`
2857
- ).join("\n")}`;
2916
+ parts.push(`Props:${req.analysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",")}`);
2858
2917
  }
2859
2918
  if (req.analysis.emits?.length) {
2919
+ parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
2920
+ }
2921
+ if (parts.length > 0) {
2860
2922
  prompt += `
2861
- Emits:
2862
- ${req.analysis.emits.map(
2863
- (e) => ` - ${e.name}${e.params?.length ? `(${e.params.join(", ")})` : ""}`
2864
- ).join("\n")}`;
2923
+ \u5206\u6790:${parts.join("|")}`;
2865
2924
  }
2866
2925
  }
2867
2926
  if (req.generationDescription) {
2868
2927
  prompt += `
2869
-
2870
- \u751F\u6210\u8005\u8BF4\u660E: ${req.generationDescription}`;
2928
+ \u8BF4\u660E:${req.generationDescription}`;
2871
2929
  }
2872
2930
  return prompt;
2873
2931
  }
@@ -2927,6 +2985,61 @@ ${req.analysis.emits.map(
2927
2985
  return urlMap[provider] || "https://api.openai.com/v1";
2928
2986
  }
2929
2987
  };
2988
+ function compressFunctionBodies(code, maxLength) {
2989
+ const lines = code.split("\n");
2990
+ const result = [];
2991
+ let depth = 0;
2992
+ let fnDepth = 0;
2993
+ let inFunction = false;
2994
+ let braceBalance = 0;
2995
+ let capturedLines = 0;
2996
+ for (const line of lines) {
2997
+ const trimmed = line.trim();
2998
+ for (const ch of trimmed) {
2999
+ if (ch === "{") {
3000
+ braceBalance++;
3001
+ depth++;
3002
+ }
3003
+ if (ch === "}") {
3004
+ braceBalance--;
3005
+ depth = Math.max(0, depth - 1);
3006
+ }
3007
+ }
3008
+ const isFnSignature = /^(export\s+)?(async\s+)?function\s|=>\s*\{|=>\s*$/m.test(trimmed) || /^(const|let|var)\s+\w+\s*=\s*(async\s+)?(\([^)]*\)|[^=])\s*=>/.test(trimmed);
3009
+ if (isFnSignature && !inFunction) {
3010
+ inFunction = true;
3011
+ fnDepth = depth;
3012
+ result.push(line);
3013
+ capturedLines++;
3014
+ continue;
3015
+ }
3016
+ if (inFunction) {
3017
+ const isReturn = trimmed.startsWith("return ");
3018
+ const isBranch = /^(if|else|switch|case|try|catch|finally|for|while)/.test(trimmed);
3019
+ const isClosing = trimmed === "}" && depth <= fnDepth - 1;
3020
+ if (isClosing) {
3021
+ result.push(line);
3022
+ inFunction = false;
3023
+ capturedLines++;
3024
+ } else if (isReturn || isBranch) {
3025
+ result.push(trimmed.length > 120 ? `${trimmed.slice(0, 120)}...` : line);
3026
+ capturedLines++;
3027
+ } else if (trimmed.startsWith("//") || trimmed === "") {
3028
+ } else if (capturedLines < maxLength / 30) {
3029
+ result.push(trimmed.length > 100 ? `${trimmed.slice(0, 100)}...` : line);
3030
+ capturedLines++;
3031
+ }
3032
+ } else {
3033
+ result.push(line);
3034
+ capturedLines++;
3035
+ }
3036
+ if (result.join("\n").length > maxLength) {
3037
+ result.push("// ... (truncated)");
3038
+ break;
3039
+ }
3040
+ }
3041
+ return result.join("\n");
3042
+ }
2930
3043
 
2931
3044
  // src/ai/provider.ts
2932
3045
  var providerRegistry = /* @__PURE__ */ new Map();