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.cjs CHANGED
@@ -2616,6 +2616,15 @@ var NoopAIProvider = class {
2616
2616
  };
2617
2617
 
2618
2618
  // src/ai/openai-provider.ts
2619
+ var import_chalk2 = __toESM(require("chalk"), 1);
2620
+ function isDebug() {
2621
+ return process.env.QAT_DEBUG === "true";
2622
+ }
2623
+ function debugLog(tag, ...args) {
2624
+ if (!isDebug()) return;
2625
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
2626
+ console.error(import_chalk2.default.gray(`[DEBUG ${timestamp}] [${tag}]`), ...args);
2627
+ }
2619
2628
  var OpenAICompatibleProvider = class {
2620
2629
  constructor(config) {
2621
2630
  this.capabilities = {
@@ -2629,25 +2638,20 @@ var OpenAICompatibleProvider = class {
2629
2638
  this.baseUrl = config.baseUrl || this.getDefaultBaseUrl(config.provider);
2630
2639
  }
2631
2640
  async generateTest(req) {
2641
+ debugLog("GENERATE", `type=${req.type} target=${req.target}`);
2632
2642
  const systemPrompt = this.buildGenerateTestSystemPrompt(req);
2633
2643
  const userPrompt = this.buildGenerateTestUserPrompt(req);
2634
2644
  const content = await this.chat(systemPrompt, userPrompt);
2635
- return this.parseGenerateTestResponse(content);
2645
+ const result = this.parseGenerateTestResponse(content);
2646
+ debugLog("GENERATE", `done code=${result.code.length}chars confidence=${result.confidence}`);
2647
+ return result;
2636
2648
  }
2637
2649
  async analyzeResult(req) {
2638
- 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
2639
- \u8F93\u51FA\u683C\u5F0F\uFF1A
2640
- 1. \u5206\u6790\u6458\u8981\uFF081-3\u53E5\u8BDD\uFF09
2641
- 2. \u6539\u8FDB\u5EFA\u8BAE\u5217\u8868\uFF08\u6BCF\u6761\u5EFA\u8BAE\u5177\u4F53\u3001\u53EF\u64CD\u4F5C\uFF09`;
2642
- const resultSummary = req.testResults.map((r) => {
2643
- const failed = r.suites.flatMap((s) => s.tests.filter((t) => t.status === "failed"));
2644
- return `\u7C7B\u578B: ${r.type}, \u72B6\u6001: ${r.status}, \u8017\u65F6: ${r.duration}ms, \u5931\u8D25\u7528\u4F8B: ${failed.length}`;
2645
- }).join("\n");
2646
- 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";
2647
- const userPrompt = `\u6D4B\u8BD5\u7ED3\u679C:
2648
- ${resultSummary}
2649
-
2650
- \u9519\u8BEF\u8BE6\u60C5:
2650
+ const systemPrompt = `\u6D4B\u8BD5\u5206\u6790\u4E13\u5BB6\u3002\u627E\u95EE\u9898\u6839\u56E0\uFF0C\u7ED9\u53EF\u64CD\u4F5C\u5EFA\u8BAE\u3002
2651
+ \u8F93\u51FA:1.\u6458\u8981(1-3\u53E5) 2.\u5EFA\u8BAE\u5217\u8868`;
2652
+ const failed = req.testResults.flatMap((r) => r.suites.flatMap((s) => s.tests.filter((t) => t.status === "failed")));
2653
+ const errorDetails = req.errorLogs?.join("\n") || failed.map((t) => `[${t.name}] ${t.error?.message}`).join("\n") || "\u65E0";
2654
+ const userPrompt = `\u5931\u8D25:${failed.length}
2651
2655
  ${errorDetails}`;
2652
2656
  const content = await this.chat(systemPrompt, userPrompt);
2653
2657
  return {
@@ -2657,23 +2661,22 @@ ${errorDetails}`;
2657
2661
  };
2658
2662
  }
2659
2663
  async suggestFix(error) {
2660
- 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
2661
- \u6BCF\u6761\u5EFA\u8BAE\u5E94\u8BE5\u5305\u542B\uFF1A
2662
- 1. \u95EE\u9898\u5B9A\u4F4D
2663
- 2. \u4FEE\u590D\u65B9\u6848
2664
- 3. \u793A\u4F8B\u4EE3\u7801\uFF08\u5982\u679C\u9002\u7528\uFF09`;
2665
- const userPrompt = `\u9519\u8BEF\u4FE1\u606F: ${error.message}
2666
- ${error.stack ? `\u5806\u6808: ${error.stack}` : ""}
2667
- ${error.expected ? `\u671F\u671B\u503C: ${error.expected}` : ""}
2668
- ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
2664
+ 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`;
2665
+ const userPrompt = `\u9519\u8BEF:${error.message}${error.stack ? `
2666
+ \u5806\u6808:${error.stack}` : ""}${error.expected ? `
2667
+ \u671F\u671B:${error.expected}` : ""}${error.actual ? `
2668
+ \u5B9E\u9645:${error.actual}` : ""}`;
2669
2669
  const content = await this.chat(systemPrompt, userPrompt);
2670
2670
  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);
2671
2671
  }
2672
2672
  async reviewTest(req) {
2673
+ debugLog("REVIEW", `target=${req.target} type=${req.testType}`);
2673
2674
  const systemPrompt = this.buildReviewTestSystemPrompt(req);
2674
2675
  const userPrompt = this.buildReviewTestUserPrompt(req);
2675
2676
  const content = await this.chat(systemPrompt, userPrompt);
2676
- return this.parseReviewTestResponse(content);
2677
+ const result = this.parseReviewTestResponse(content);
2678
+ debugLog("REVIEW", `approved=${result.approved} score=${result.score} feedback=${result.feedback}`);
2679
+ return result;
2677
2680
  }
2678
2681
  // ─── 内部方法 ──────────────────────────────────────────────
2679
2682
  /**
@@ -2737,8 +2740,9 @@ ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
2737
2740
  return { ok: false, message: `\u8FDE\u63A5\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`, latencyMs };
2738
2741
  }
2739
2742
  }
2740
- async chat(systemPrompt, userPrompt) {
2743
+ async chat(systemPrompt, userPrompt, retries = 2) {
2741
2744
  const url = `${this.baseUrl}/chat/completions`;
2745
+ const useStream = isDebug();
2742
2746
  const body = {
2743
2747
  model: this.model,
2744
2748
  messages: [
@@ -2748,101 +2752,189 @@ ${error.actual ? `\u5B9E\u9645\u503C: ${error.actual}` : ""}`;
2748
2752
  temperature: 0.3,
2749
2753
  max_tokens: 4096
2750
2754
  };
2755
+ if (useStream) {
2756
+ body.stream = true;
2757
+ }
2751
2758
  const headers = {
2752
2759
  "Content-Type": "application/json"
2753
2760
  };
2754
2761
  if (this.apiKey) {
2755
2762
  headers["Authorization"] = `Bearer ${this.apiKey}`;
2756
2763
  }
2757
- const response = await fetch(url, {
2758
- method: "POST",
2759
- headers,
2760
- body: JSON.stringify(body),
2761
- signal: AbortSignal.timeout(6e4)
2762
- // 60s timeout
2763
- });
2764
- if (!response.ok) {
2765
- const text = await response.text().catch(() => "");
2766
- throw new Error(`AI API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${text.slice(0, 500)}`);
2764
+ debugLog("REQUEST", `POST ${url}`);
2765
+ debugLog("REQUEST", `model=${this.model} stream=${useStream}`);
2766
+ debugLog("SYSTEM", systemPrompt.length > 500 ? `${systemPrompt.slice(0, 500)}...` : systemPrompt);
2767
+ debugLog("USER", userPrompt.length > 1e3 ? `${userPrompt.slice(0, 1e3)}...` : userPrompt);
2768
+ let lastError = null;
2769
+ for (let attempt = 0; attempt <= retries; attempt++) {
2770
+ try {
2771
+ if (attempt > 0) {
2772
+ debugLog("RETRY", `\u7B2C${attempt}\u6B21\u91CD\u8BD5...`);
2773
+ }
2774
+ const response = await fetch(url, {
2775
+ method: "POST",
2776
+ headers,
2777
+ body: JSON.stringify(body),
2778
+ signal: AbortSignal.timeout(12e4)
2779
+ // 120s timeout
2780
+ });
2781
+ if (!response.ok) {
2782
+ const text = await response.text().catch(() => "");
2783
+ if ((response.status === 429 || response.status >= 500) && attempt < retries) {
2784
+ const delay = Math.min(1e3 * Math.pow(2, attempt), 8e3);
2785
+ debugLog("RETRY", `HTTP ${response.status}, ${delay}ms\u540E\u91CD\u8BD5`);
2786
+ await new Promise((r) => setTimeout(r, delay));
2787
+ lastError = new Error(`AI API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${text.slice(0, 200)}`);
2788
+ continue;
2789
+ }
2790
+ throw new Error(`AI API \u8BF7\u6C42\u5931\u8D25 (${response.status}): ${text.slice(0, 500)}`);
2791
+ }
2792
+ if (useStream && response.body) {
2793
+ const content = await this.readStream(response.body);
2794
+ debugLog("RESPONSE", content.length > 500 ? `${content.slice(0, 500)}...` : content);
2795
+ return content;
2796
+ }
2797
+ const data = await response.json();
2798
+ if (!data.choices?.[0]?.message?.content) {
2799
+ throw new Error("AI API \u8FD4\u56DE\u7A7A\u54CD\u5E94");
2800
+ }
2801
+ debugLog("RESPONSE", `tokens: prompt=${data.usage?.prompt_tokens} completion=${data.usage?.completion_tokens} total=${data.usage?.total_tokens}`);
2802
+ debugLog("RESPONSE", data.choices[0].message.content.length > 500 ? `${data.choices[0].message.content.slice(0, 500)}...` : data.choices[0].message.content);
2803
+ return data.choices[0].message.content;
2804
+ } catch (error) {
2805
+ if (error instanceof Error && error.name === "TimeoutError" && attempt < retries) {
2806
+ debugLog("TIMEOUT", `\u7B2C${attempt}\u6B21\u8D85\u65F6\uFF0C\u91CD\u8BD5\u4E2D...`);
2807
+ lastError = error;
2808
+ continue;
2809
+ }
2810
+ throw error;
2811
+ }
2767
2812
  }
2768
- const data = await response.json();
2769
- if (!data.choices?.[0]?.message?.content) {
2770
- throw new Error("AI API \u8FD4\u56DE\u7A7A\u54CD\u5E94");
2813
+ throw lastError || new Error("AI API \u8BF7\u6C42\u5931\u8D25");
2814
+ }
2815
+ /**
2816
+ * 读取 SSE 流式响应,实时输出内容
2817
+ */
2818
+ async readStream(body) {
2819
+ const chunks = [];
2820
+ const decoder = new TextDecoder();
2821
+ const reader = body.getReader();
2822
+ let buffer = "";
2823
+ let lineCount = 0;
2824
+ try {
2825
+ while (true) {
2826
+ const { done, value } = await reader.read();
2827
+ if (done) break;
2828
+ buffer += decoder.decode(value, { stream: true });
2829
+ const lines = buffer.split("\n");
2830
+ buffer = lines.pop() || "";
2831
+ for (const line of lines) {
2832
+ const trimmed = line.trim();
2833
+ if (!trimmed || trimmed === "data: [DONE]") continue;
2834
+ if (!trimmed.startsWith("data: ")) continue;
2835
+ try {
2836
+ const json = JSON.parse(trimmed.slice(6));
2837
+ const delta = json.choices?.[0]?.delta?.content;
2838
+ if (delta) {
2839
+ chunks.push(delta);
2840
+ lineCount++;
2841
+ process.stderr.write(import_chalk2.default.gray(delta));
2842
+ if (lineCount % 20 === 0) {
2843
+ process.stderr.write("\n");
2844
+ }
2845
+ }
2846
+ if (json.choices?.[0]?.finish_reason === "stop") {
2847
+ debugLog("STREAM", "\u5B8C\u6210");
2848
+ }
2849
+ } catch {
2850
+ }
2851
+ }
2852
+ }
2853
+ } finally {
2854
+ reader.releaseLock();
2855
+ }
2856
+ if (chunks.length > 0) {
2857
+ process.stderr.write("\n");
2771
2858
  }
2772
- return data.choices[0].message.content;
2859
+ return chunks.join("");
2860
+ }
2861
+ /**
2862
+ * 压缩源码:保留签名和关键逻辑,剔除注释、空行、样式块
2863
+ * 目标:在保留准确性的前提下减少 token 消耗
2864
+ */
2865
+ compressSourceCode(code, maxLength = 3e3) {
2866
+ let compressed = code;
2867
+ compressed = compressed.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
2868
+ compressed = compressed.replace(/<template[^>]*>([\s\S]*?)<\/template>/gi, (_match, content) => {
2869
+ return `<template>${content.replace(/\s*(?:class|style)\s*=\s*["'][^"']*["']/gi, "")}</template>`;
2870
+ });
2871
+ compressed = compressed.replace(/\/\*[\s\S]*?\*\//g, "");
2872
+ compressed = compressed.replace(/(^|[^:])(\/\/.*$)/gm, "$1");
2873
+ compressed = compressed.replace(/\n\s*\n\s*\n/g, "\n\n");
2874
+ compressed = compressed.split("\n").map((line) => line.trimEnd()).join("\n").trim();
2875
+ if (compressed.length > maxLength) {
2876
+ const scriptMatch = compressed.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
2877
+ const templateMatch = compressed.match(/<template[^>]*>([\s\S]*?)<\/template>/i);
2878
+ if (scriptMatch) {
2879
+ let scriptPart = scriptMatch[1].trim();
2880
+ scriptPart = compressFunctionBodies(scriptPart, maxLength * 0.7);
2881
+ const templatePart = templateMatch ? `<template>${templateMatch[1].replace(/\s+/g, " ").trim().slice(0, 300)}...</template>` : "";
2882
+ compressed = `${templatePart}
2883
+ <script${scriptMatch[0].match(/<script[^>]*>/)?.[0]?.slice(7) || ">"}>${scriptPart}</script>`;
2884
+ } else {
2885
+ compressed = compressFunctionBodies(compressed, maxLength);
2886
+ }
2887
+ }
2888
+ return compressed;
2773
2889
  }
2774
2890
  buildGenerateTestSystemPrompt(req) {
2775
2891
  const typeMap = {
2776
- unit: "\u5355\u5143\u6D4B\u8BD5\uFF08Vitest + @vue/test-utils\uFF09",
2777
- component: "\u7EC4\u4EF6\u6D4B\u8BD5\uFF08Vitest + @vue/test-utils + mount\uFF09",
2778
- e2e: "E2E\u7AEF\u5230\u7AEF\u6D4B\u8BD5\uFF08Playwright\uFF09",
2779
- api: "API\u63A5\u53E3\u6D4B\u8BD5\uFF08Vitest + fetch\uFF09",
2780
- visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5\uFF08Playwright screenshot\uFF09",
2781
- performance: "\u6027\u80FD\u6D4B\u8BD5\uFF08Playwright + performance metrics\uFF09"
2892
+ unit: "\u5355\u5143\u6D4B\u8BD5(Vitest)",
2893
+ component: "\u7EC4\u4EF6\u6D4B\u8BD5(Vitest+@vue/test-utils)",
2894
+ e2e: "E2E\u6D4B\u8BD5(Playwright)",
2895
+ api: "API\u6D4B\u8BD5(Vitest+fetch)",
2896
+ visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5(Playwright)",
2897
+ performance: "\u6027\u80FD\u6D4B\u8BD5(Playwright)"
2782
2898
  };
2783
- 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
2784
- \u8981\u6C42\uFF1A
2785
- 1. \u53EA\u8F93\u51FA\u6D4B\u8BD5\u4EE3\u7801\uFF0C\u4E0D\u8981\u591A\u4F59\u7684\u89E3\u91CA
2786
- 2. \u4EE3\u7801\u5FC5\u987B\u53EF\u76F4\u63A5\u8FD0\u884C\uFF0C\u5305\u542B\u6240\u6709\u5FC5\u8981\u7684 import
2787
- 3. \u6D4B\u8BD5\u7528\u4F8B\u8986\u76D6\uFF1A\u6B63\u5E38\u8DEF\u5F84\u3001\u8FB9\u754C\u6761\u4EF6\u3001\u9519\u8BEF\u5904\u7406
2788
- 4. \u4F7F\u7528\u4E2D\u6587\u63CF\u8FF0 it/test \u5757\u540D\u79F0
2789
- 5. Vue \u7EC4\u4EF6\u6D4B\u8BD5\u4F7F\u7528 @vue/test-utils \u7684 mount
2790
- 6. \u5982\u679C\u6709 props/emits \u4FE1\u606F\uFF0C\u52A1\u5FC5\u9488\u5BF9\u6BCF\u4E2A prop \u548C emit \u751F\u6210\u6D4B\u8BD5`;
2899
+ return `\u4F60\u662F\u524D\u7AEF\u6D4B\u8BD5\u5DE5\u7A0B\u5E08\uFF0C\u7F16\u5199${typeMap[req.type] || req.type}\u3002
2900
+ \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`;
2791
2901
  }
2792
2902
  buildGenerateTestUserPrompt(req) {
2793
2903
  const importPath = this.computeTestImportPath(req.type, req.target);
2794
- let prompt = `\u8BF7\u4E3A\u4EE5\u4E0B\u6587\u4EF6\u751F\u6210${req.type}\u6D4B\u8BD5\u4EE3\u7801:
2795
- \u76EE\u6807\u6587\u4EF6: ${req.target}
2796
- \u6D4B\u8BD5\u6587\u4EF6\u5C06\u653E\u5728: ${this.getTestOutputDir(req.type)}/
2797
- \u6B63\u786E\u7684 import \u8DEF\u5F84: ${importPath}
2798
-
2799
- \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
2904
+ let prompt = `\u4E3A${req.target}\u751F\u6210${req.type}\u6D4B\u8BD5\u3002import\u8DEF\u5F84:${importPath}
2800
2905
  `;
2801
2906
  if (req.analysis) {
2802
- prompt += "\n\u6E90\u7801\u5206\u6790\u7ED3\u679C:\n";
2907
+ const parts = [];
2803
2908
  if (req.analysis.exports?.length > 0) {
2804
- prompt += `\u5BFC\u51FA\u9879:
2805
- ${req.analysis.exports.map((e) => {
2806
- const params = e.params?.length ? `(${e.params.join(", ")})` : "";
2807
- const asyncFlag = e.isAsync ? "async " : "";
2808
- return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;
2809
- }).join("\n")}
2810
- `;
2909
+ parts.push(`\u5BFC\u51FA:${req.analysis.exports.map((e) => {
2910
+ const p = e.params?.length ? `(${e.params.join(",")})` : "";
2911
+ return `${e.isAsync ? "async " : ""}${e.name}${p}[${e.kind}]`;
2912
+ }).join(",")}`);
2811
2913
  }
2812
2914
  if (req.analysis.props?.length) {
2813
- prompt += `Props:
2814
- ${req.analysis.props.map(
2815
- (p) => ` - ${p.name}: ${p.type}${p.required ? " (\u5FC5\u586B)" : " (\u53EF\u9009)"}`
2816
- ).join("\n")}
2817
- `;
2915
+ parts.push(`Props:${req.analysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",")}`);
2818
2916
  }
2819
2917
  if (req.analysis.emits?.length) {
2820
- prompt += `Emits:
2821
- ${req.analysis.emits.map(
2822
- (e) => ` - ${e.name}${e.params?.length ? `(${e.params.join(", ")})` : ""}`
2823
- ).join("\n")}
2824
- `;
2918
+ parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
2825
2919
  }
2826
2920
  if (req.analysis.methods?.length) {
2827
- prompt += `Methods: ${req.analysis.methods.join(", ")}
2828
- `;
2921
+ parts.push(`Methods:${req.analysis.methods.join(",")}`);
2829
2922
  }
2830
2923
  if (req.analysis.computed?.length) {
2831
- prompt += `Computed: ${req.analysis.computed.join(", ")}
2832
- `;
2924
+ parts.push(`Computed:${req.analysis.computed.join(",")}`);
2833
2925
  }
2926
+ prompt += parts.join("\n") + "\n";
2834
2927
  }
2835
2928
  if (req.context) {
2836
2929
  prompt += `
2837
- \u6E90\u7801\u5185\u5BB9:
2838
- \`\`\`typescript
2839
- ${req.context}
2930
+ \u6E90\u7801:
2931
+ \`\`\`
2932
+ ${this.compressSourceCode(req.context)}
2840
2933
  \`\`\`
2841
2934
  `;
2842
2935
  }
2843
2936
  if (req.framework) {
2844
- prompt += `
2845
- \u6846\u67B6: ${req.framework}`;
2937
+ prompt += `\u6846\u67B6:${req.framework}`;
2846
2938
  }
2847
2939
  return prompt;
2848
2940
  }
@@ -2872,20 +2964,6 @@ ${req.context}
2872
2964
  }
2873
2965
  return `${prefix}${cleanPath}`;
2874
2966
  }
2875
- /**
2876
- * 获取测试输出目录
2877
- */
2878
- getTestOutputDir(testType) {
2879
- const dirMap = {
2880
- unit: "tests/unit",
2881
- component: "tests/component",
2882
- e2e: "tests/e2e",
2883
- api: "tests/api",
2884
- visual: "tests/visual",
2885
- performance: "tests/e2e"
2886
- };
2887
- return dirMap[testType] || "tests/unit";
2888
- }
2889
2967
  parseGenerateTestResponse(content) {
2890
2968
  const codeBlockMatch = content.match(/```(?:typescript|ts|javascript|js)?\s*\n([\s\S]*?)```/);
2891
2969
  const code = codeBlockMatch ? codeBlockMatch[1].trim() : content.replace(/^(?:```[\s\S]*?\n)?/, "").replace(/\n?```$/, "").trim();
@@ -2897,69 +2975,49 @@ ${req.context}
2897
2975
  };
2898
2976
  }
2899
2977
  buildReviewTestSystemPrompt(_req) {
2900
- 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
2901
-
2902
- \u5BA1\u67E5\u6807\u51C6\uFF1A
2903
- 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
2904
- 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
2905
- 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
2906
- 4. **\u5BFC\u5165\u6B63\u786E\u6027**\uFF1Aimport \u8DEF\u5F84\u548C\u6A21\u5757\u662F\u5426\u6B63\u786E
2907
- 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
2908
-
2909
- \u8F93\u51FA\u683C\u5F0F\uFF08\u4E25\u683C\u9075\u5B88\uFF09\uFF1A
2910
- APPROVED: true \u6216 false
2911
- SCORE: 0.0 \u5230 1.0 \u4E4B\u95F4\u7684\u8BC4\u5206
2912
- FEEDBACK: \u4E00\u53E5\u8BDD\u5BA1\u8BA1\u610F\u89C1
2913
- ISSUES: \u95EE\u9898\u5217\u8868\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF0C\u683C\u5F0F "- \u95EE\u9898\u63CF\u8FF0"\uFF09
2914
- SUGGESTIONS: \u6539\u8FDB\u5EFA\u8BAE\u5217\u8868\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF0C\u683C\u5F0F "- \u5EFA\u8BAE\u63CF\u8FF0"\uFF09`;
2978
+ return `\u4F60\u662F\u6D4B\u8BD5\u5BA1\u8BA1\u4E13\u5BB6\u3002\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u662F\u5426\u51C6\u786E\u3002
2979
+ \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
2980
+ \u8F93\u51FA:
2981
+ APPROVED:true\u6216false
2982
+ SCORE:0.0-1.0
2983
+ FEEDBACK:\u4E00\u53E5\u8BDD
2984
+ ISSUES:- \u95EE\u9898(\u6BCF\u884C\u4E00\u4E2A)
2985
+ SUGGESTIONS:- \u5EFA\u8BAE(\u6BCF\u884C\u4E00\u4E2A)`;
2915
2986
  }
2916
2987
  buildReviewTestUserPrompt(req) {
2917
2988
  const importPath = this.computeTestImportPath(req.testType, req.target);
2918
- let prompt = `\u8BF7\u5BA1\u67E5\u4EE5\u4E0B\u6D4B\u8BD5\u7528\u4F8B\u662F\u5426\u4E0E\u6E90\u7801\u8D34\u5207\u4E14\u51C6\u786E\u3002
2919
-
2920
- \u88AB\u6D4B\u6587\u4EF6: ${req.target}
2921
- \u6D4B\u8BD5\u7C7B\u578B: ${req.testType}
2922
- \u6B63\u786E\u7684 import \u8DEF\u5F84\u5E94\u4E3A: ${importPath}\uFF08\u6D4B\u8BD5\u6587\u4EF6\u4F4D\u4E8E ${this.getTestOutputDir(req.testType)}/\uFF09
2923
-
2924
- --- \u6E90\u7801\u5185\u5BB9 ---
2925
- \`\`\`typescript
2926
- ${req.sourceCode}
2989
+ let prompt = `\u5BA1\u67E5\u6D4B\u8BD5\u4EE3\u7801\u3002\u88AB\u6D4B:${req.target} \u7C7B\u578B:${req.testType} import\u8DEF\u5F84:${importPath}
2990
+ `;
2991
+ prompt += `
2992
+ \u6E90\u7801:
2993
+ \`\`\`
2994
+ ${this.compressSourceCode(req.sourceCode, 2e3)}
2995
+ \`\`\`
2996
+ `;
2997
+ prompt += `
2998
+ \u6D4B\u8BD5\u4EE3\u7801:
2927
2999
  \`\`\`
2928
-
2929
- --- \u751F\u6210\u7684\u6D4B\u8BD5\u4EE3\u7801 ---
2930
- \`\`\`typescript
2931
3000
  ${req.testCode}
2932
3001
  \`\`\``;
2933
3002
  if (req.analysis) {
2934
- prompt += "\n\n--- \u6E90\u7801\u5206\u6790 ---";
3003
+ const parts = [];
2935
3004
  if (req.analysis.exports?.length > 0) {
2936
- prompt += `
2937
- \u5BFC\u51FA\u9879:
2938
- ${req.analysis.exports.map((e) => {
2939
- const params = e.params?.length ? `(${e.params.join(", ")})` : "";
2940
- const asyncFlag = e.isAsync ? "async " : "";
2941
- return ` - ${asyncFlag}${e.name}${params} [${e.kind}]`;
2942
- }).join("\n")}`;
3005
+ parts.push(`\u5BFC\u51FA:${req.analysis.exports.map((e) => `${e.isAsync ? "async " : ""}${e.name}(${e.params?.join(",") || ""})[${e.kind}]`).join(",")}`);
2943
3006
  }
2944
3007
  if (req.analysis.props?.length) {
2945
- prompt += `
2946
- Props:
2947
- ${req.analysis.props.map(
2948
- (p) => ` - ${p.name}: ${p.type}${p.required ? " (\u5FC5\u586B)" : " (\u53EF\u9009)"}`
2949
- ).join("\n")}`;
3008
+ parts.push(`Props:${req.analysis.props.map((p) => `${p.name}:${p.type}${p.required ? "!" : "?"}`).join(",")}`);
2950
3009
  }
2951
3010
  if (req.analysis.emits?.length) {
3011
+ parts.push(`Emits:${req.analysis.emits.map((e) => `${e.name}(${e.params?.join(",") || ""})`).join(",")}`);
3012
+ }
3013
+ if (parts.length > 0) {
2952
3014
  prompt += `
2953
- Emits:
2954
- ${req.analysis.emits.map(
2955
- (e) => ` - ${e.name}${e.params?.length ? `(${e.params.join(", ")})` : ""}`
2956
- ).join("\n")}`;
3015
+ \u5206\u6790:${parts.join("|")}`;
2957
3016
  }
2958
3017
  }
2959
3018
  if (req.generationDescription) {
2960
3019
  prompt += `
2961
-
2962
- \u751F\u6210\u8005\u8BF4\u660E: ${req.generationDescription}`;
3020
+ \u8BF4\u660E:${req.generationDescription}`;
2963
3021
  }
2964
3022
  return prompt;
2965
3023
  }
@@ -3019,6 +3077,61 @@ ${req.analysis.emits.map(
3019
3077
  return urlMap[provider] || "https://api.openai.com/v1";
3020
3078
  }
3021
3079
  };
3080
+ function compressFunctionBodies(code, maxLength) {
3081
+ const lines = code.split("\n");
3082
+ const result = [];
3083
+ let depth = 0;
3084
+ let fnDepth = 0;
3085
+ let inFunction = false;
3086
+ let braceBalance = 0;
3087
+ let capturedLines = 0;
3088
+ for (const line of lines) {
3089
+ const trimmed = line.trim();
3090
+ for (const ch of trimmed) {
3091
+ if (ch === "{") {
3092
+ braceBalance++;
3093
+ depth++;
3094
+ }
3095
+ if (ch === "}") {
3096
+ braceBalance--;
3097
+ depth = Math.max(0, depth - 1);
3098
+ }
3099
+ }
3100
+ 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);
3101
+ if (isFnSignature && !inFunction) {
3102
+ inFunction = true;
3103
+ fnDepth = depth;
3104
+ result.push(line);
3105
+ capturedLines++;
3106
+ continue;
3107
+ }
3108
+ if (inFunction) {
3109
+ const isReturn = trimmed.startsWith("return ");
3110
+ const isBranch = /^(if|else|switch|case|try|catch|finally|for|while)/.test(trimmed);
3111
+ const isClosing = trimmed === "}" && depth <= fnDepth - 1;
3112
+ if (isClosing) {
3113
+ result.push(line);
3114
+ inFunction = false;
3115
+ capturedLines++;
3116
+ } else if (isReturn || isBranch) {
3117
+ result.push(trimmed.length > 120 ? `${trimmed.slice(0, 120)}...` : line);
3118
+ capturedLines++;
3119
+ } else if (trimmed.startsWith("//") || trimmed === "") {
3120
+ } else if (capturedLines < maxLength / 30) {
3121
+ result.push(trimmed.length > 100 ? `${trimmed.slice(0, 100)}...` : line);
3122
+ capturedLines++;
3123
+ }
3124
+ } else {
3125
+ result.push(line);
3126
+ capturedLines++;
3127
+ }
3128
+ if (result.join("\n").length > maxLength) {
3129
+ result.push("// ... (truncated)");
3130
+ break;
3131
+ }
3132
+ }
3133
+ return result.join("\n");
3134
+ }
3022
3135
 
3023
3136
  // src/ai/provider.ts
3024
3137
  var providerRegistry = /* @__PURE__ */ new Map();