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/README.md +1 -1
- package/dist/cli.js +507 -387
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +259 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.js +259 -146
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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 = `\
|
|
2639
|
-
\u8F93\u51FA\
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
const
|
|
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 = `\
|
|
2661
|
-
\
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
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
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
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
|
|
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
|
|
2777
|
-
component: "\u7EC4\u4EF6\u6D4B\u8BD5
|
|
2778
|
-
e2e: "E2E\
|
|
2779
|
-
api: "API\
|
|
2780
|
-
visual: "\u89C6\u89C9\u56DE\u5F52\u6D4B\u8BD5
|
|
2781
|
-
performance: "\u6027\u80FD\u6D4B\u8BD5
|
|
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\
|
|
2784
|
-
\
|
|
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 = `\
|
|
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
|
-
|
|
2907
|
+
const parts = [];
|
|
2803
2908
|
if (req.analysis.exports?.length > 0) {
|
|
2804
|
-
|
|
2805
|
-
${
|
|
2806
|
-
|
|
2807
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2828
|
-
`;
|
|
2921
|
+
parts.push(`Methods:${req.analysis.methods.join(",")}`);
|
|
2829
2922
|
}
|
|
2830
2923
|
if (req.analysis.computed?.length) {
|
|
2831
|
-
|
|
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
|
|
2838
|
-
\`\`\`
|
|
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\
|
|
2901
|
-
|
|
2902
|
-
\
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
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 = `\
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
\
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
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
|
-
|
|
3003
|
+
const parts = [];
|
|
2935
3004
|
if (req.analysis.exports?.length > 0) {
|
|
2936
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|