@zjex/git-workflow 0.4.6 → 0.5.0
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/CHANGELOG.md +136 -377
- package/README.md +44 -6
- package/dist/index.js +675 -31
- package/docs/.vitepress/cache/deps/_metadata.json +10 -10
- package/docs/.vitepress/config.ts +4 -0
- package/docs/commands/index.md +4 -0
- package/docs/commands/review.md +142 -0
- package/docs/guide/ai-review.md +159 -0
- package/docs/guide/index.md +2 -0
- package/docs/index.md +26 -3
- package/package.json +1 -1
- package/scripts/generate-changelog-manual.js +15 -64
- package/src/commands/review.ts +759 -0
- package/src/commands/tag.ts +42 -10
- package/src/commands/update.ts +25 -9
- package/src/index.ts +29 -1
- package/src/utils.ts +3 -1
- package/tests/review.test.ts +1058 -0
- package/tests/update.test.ts +85 -69
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-2CLQ7TTZ.js +0 -9719
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-2CLQ7TTZ.js.map +0 -7
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-LE5NDSFD.js +0 -12824
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-LE5NDSFD.js.map +0 -7
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/package.json +0 -3
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vue_devtools-api.js +0 -4505
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vue_devtools-api.js.map +0 -7
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_core.js +0 -583
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_core.js.map +0 -7
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_integrations_useFocusTrap.js +0 -1352
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_integrations_useFocusTrap.js.map +0 -7
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___mark__js_src_vanilla__js.js +0 -1665
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___mark__js_src_vanilla__js.js.map +0 -7
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___minisearch.js +0 -1813
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___minisearch.js.map +0 -7
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vue.js +0 -347
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vue.js.map +0 -7
package/dist/index.js
CHANGED
|
@@ -84,7 +84,8 @@ function execAsync(command, spinner) {
|
|
|
84
84
|
${stdoutOutput}`));
|
|
85
85
|
}
|
|
86
86
|
if (errorOutput) {
|
|
87
|
-
|
|
87
|
+
const label = code === 0 ? "\u8F93\u51FA\u4FE1\u606F" : "\u9519\u8BEF\u8F93\u51FA";
|
|
88
|
+
console.log(colors.dim(`[DEBUG] ${label}:
|
|
88
89
|
${errorOutput}`));
|
|
89
90
|
}
|
|
90
91
|
}
|
|
@@ -406,7 +407,7 @@ var init_update_notifier = __esm({
|
|
|
406
407
|
// src/index.ts
|
|
407
408
|
init_utils();
|
|
408
409
|
import { cac } from "cac";
|
|
409
|
-
import { select as
|
|
410
|
+
import { select as select11 } from "@inquirer/prompts";
|
|
410
411
|
import { ExitPromptError } from "@inquirer/core";
|
|
411
412
|
|
|
412
413
|
// src/commands/branch.ts
|
|
@@ -1059,6 +1060,25 @@ async function createTag(inputPrefix) {
|
|
|
1059
1060
|
}
|
|
1060
1061
|
async function doCreateTag(tagName) {
|
|
1061
1062
|
divider();
|
|
1063
|
+
const hasCommits = execOutput("git rev-parse HEAD 2>/dev/null");
|
|
1064
|
+
if (!hasCommits) {
|
|
1065
|
+
console.log(colors.red("\u5F53\u524D\u4ED3\u5E93\u6CA1\u6709\u4EFB\u4F55\u63D0\u4EA4"));
|
|
1066
|
+
console.log("");
|
|
1067
|
+
console.log(colors.dim(" \u63D0\u793A: \u9700\u8981\u5148\u521B\u5EFA\u81F3\u5C11\u4E00\u4E2A\u63D0\u4EA4\u624D\u80FD\u6253 tag:"));
|
|
1068
|
+
console.log(colors.cyan(" git add ."));
|
|
1069
|
+
console.log(colors.cyan(' git commit -m "Initial commit"'));
|
|
1070
|
+
console.log(colors.cyan(" gw tag"));
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const existingTags = execOutput("git tag -l").split("\n").filter(Boolean);
|
|
1074
|
+
if (existingTags.includes(tagName)) {
|
|
1075
|
+
console.log(colors.red(`Tag ${tagName} \u5DF2\u5B58\u5728`));
|
|
1076
|
+
console.log("");
|
|
1077
|
+
console.log(colors.dim(" \u63D0\u793A: \u5982\u9700\u91CD\u65B0\u521B\u5EFA\uFF0C\u8BF7\u5148\u5220\u9664\u65E7 tag:"));
|
|
1078
|
+
console.log(colors.cyan(` git tag -d ${tagName}`));
|
|
1079
|
+
console.log(colors.cyan(` git push origin --delete ${tagName}`));
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1062
1082
|
const spinner = ora2(`\u6B63\u5728\u521B\u5EFA tag: ${tagName}`).start();
|
|
1063
1083
|
const success = await execWithSpinner(
|
|
1064
1084
|
`git tag -a "${tagName}" -m "Release ${tagName}"`,
|
|
@@ -1206,9 +1226,12 @@ async function updateTag() {
|
|
|
1206
1226
|
spinner.fail("tag \u91CD\u547D\u540D\u5931\u8D25");
|
|
1207
1227
|
return;
|
|
1208
1228
|
}
|
|
1209
|
-
const
|
|
1210
|
-
if (!
|
|
1229
|
+
const deleteResult = await execAsync(`git tag -d "${oldTag}"`, spinner);
|
|
1230
|
+
if (!deleteResult.success) {
|
|
1211
1231
|
spinner.fail("\u5220\u9664\u65E7 tag \u5931\u8D25");
|
|
1232
|
+
if (deleteResult.error) {
|
|
1233
|
+
console.log(colors.dim(` ${deleteResult.error}`));
|
|
1234
|
+
}
|
|
1212
1235
|
return;
|
|
1213
1236
|
}
|
|
1214
1237
|
spinner.succeed(`Tag \u5DF2\u91CD\u547D\u540D: ${oldTag} \u2192 ${newTag}`);
|
|
@@ -1222,26 +1245,32 @@ async function updateTag() {
|
|
|
1222
1245
|
});
|
|
1223
1246
|
if (pushRemote) {
|
|
1224
1247
|
const pushSpinner = ora2("\u6B63\u5728\u540C\u6B65\u5230\u8FDC\u7A0B...").start();
|
|
1225
|
-
const
|
|
1248
|
+
const pushNewResult = await execAsync(
|
|
1226
1249
|
`git push origin "${newTag}"`,
|
|
1227
1250
|
pushSpinner
|
|
1228
1251
|
);
|
|
1229
|
-
if (!
|
|
1252
|
+
if (!pushNewResult.success) {
|
|
1230
1253
|
pushSpinner.warn(
|
|
1231
1254
|
`\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25\uFF0C\u53EF\u7A0D\u540E\u624B\u52A8\u6267\u884C:
|
|
1232
1255
|
git push origin ${newTag}
|
|
1233
1256
|
git push origin --delete ${oldTag}`
|
|
1234
1257
|
);
|
|
1258
|
+
if (pushNewResult.error) {
|
|
1259
|
+
console.log(colors.dim(` ${pushNewResult.error}`));
|
|
1260
|
+
}
|
|
1235
1261
|
return;
|
|
1236
1262
|
}
|
|
1237
|
-
const
|
|
1263
|
+
const deleteOldResult = await execAsync(
|
|
1238
1264
|
`git push origin --delete "${oldTag}"`,
|
|
1239
1265
|
pushSpinner
|
|
1240
1266
|
);
|
|
1241
|
-
if (!
|
|
1267
|
+
if (!deleteOldResult.success) {
|
|
1242
1268
|
pushSpinner.warn(
|
|
1243
1269
|
`\u8FDC\u7A0B\u65E7 tag \u5220\u9664\u5931\u8D25\uFF0C\u53EF\u7A0D\u540E\u624B\u52A8\u6267\u884C: git push origin --delete ${oldTag}`
|
|
1244
1270
|
);
|
|
1271
|
+
if (deleteOldResult.error) {
|
|
1272
|
+
console.log(colors.dim(` ${deleteOldResult.error}`));
|
|
1273
|
+
}
|
|
1245
1274
|
return;
|
|
1246
1275
|
}
|
|
1247
1276
|
pushSpinner.succeed(`\u8FDC\u7A0B tag \u5DF2\u540C\u6B65: ${oldTag} \u2192 ${newTag}`);
|
|
@@ -1294,8 +1323,8 @@ async function cleanInvalidTags() {
|
|
|
1294
1323
|
let localSuccess = 0;
|
|
1295
1324
|
let localFailed = 0;
|
|
1296
1325
|
for (const tag of invalidTags) {
|
|
1297
|
-
const
|
|
1298
|
-
if (success) {
|
|
1326
|
+
const result = await execAsync(`git tag -d "${tag}"`, localSpinner);
|
|
1327
|
+
if (result.success) {
|
|
1299
1328
|
localSuccess++;
|
|
1300
1329
|
} else {
|
|
1301
1330
|
localFailed++;
|
|
@@ -1321,11 +1350,11 @@ async function cleanInvalidTags() {
|
|
|
1321
1350
|
let remoteSuccess = 0;
|
|
1322
1351
|
let remoteFailed = 0;
|
|
1323
1352
|
for (const tag of invalidTags) {
|
|
1324
|
-
const
|
|
1353
|
+
const result = await execAsync(
|
|
1325
1354
|
`git push origin --delete "${tag}"`,
|
|
1326
1355
|
remoteSpinner
|
|
1327
1356
|
);
|
|
1328
|
-
if (success) {
|
|
1357
|
+
if (result.success) {
|
|
1329
1358
|
remoteSuccess++;
|
|
1330
1359
|
} else {
|
|
1331
1360
|
remoteFailed++;
|
|
@@ -2806,16 +2835,28 @@ function clearUpdateCache2() {
|
|
|
2806
2835
|
}
|
|
2807
2836
|
}
|
|
2808
2837
|
async function getLatestVersion2(packageName) {
|
|
2809
|
-
|
|
2810
|
-
const
|
|
2811
|
-
|
|
2812
|
-
timeout:
|
|
2813
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
2838
|
+
return new Promise((resolve) => {
|
|
2839
|
+
const npmView = spawn3("npm", ["view", packageName, "version"], {
|
|
2840
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2841
|
+
timeout: 5e3
|
|
2814
2842
|
});
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2843
|
+
let output = "";
|
|
2844
|
+
if (npmView.stdout) {
|
|
2845
|
+
npmView.stdout.on("data", (data) => {
|
|
2846
|
+
output += data.toString();
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
npmView.on("close", (code) => {
|
|
2850
|
+
if (code === 0 && output.trim()) {
|
|
2851
|
+
resolve(output.trim());
|
|
2852
|
+
} else {
|
|
2853
|
+
resolve(null);
|
|
2854
|
+
}
|
|
2855
|
+
});
|
|
2856
|
+
npmView.on("error", () => {
|
|
2857
|
+
resolve(null);
|
|
2858
|
+
});
|
|
2859
|
+
});
|
|
2819
2860
|
}
|
|
2820
2861
|
function isUsingVolta2() {
|
|
2821
2862
|
try {
|
|
@@ -3561,6 +3602,589 @@ fi
|
|
|
3561
3602
|
}
|
|
3562
3603
|
}
|
|
3563
3604
|
|
|
3605
|
+
// src/commands/review.ts
|
|
3606
|
+
init_utils();
|
|
3607
|
+
import { select as select10, checkbox as checkbox2 } from "@inquirer/prompts";
|
|
3608
|
+
import ora7 from "ora";
|
|
3609
|
+
import { writeFileSync as writeFileSync5, existsSync as existsSync5, mkdirSync } from "fs";
|
|
3610
|
+
import { join as join6 } from "path";
|
|
3611
|
+
var AI_PROVIDERS2 = {
|
|
3612
|
+
github: {
|
|
3613
|
+
name: "GitHub Models",
|
|
3614
|
+
endpoint: "https://models.github.ai/inference/chat/completions",
|
|
3615
|
+
defaultModel: "gpt-4o"
|
|
3616
|
+
},
|
|
3617
|
+
openai: {
|
|
3618
|
+
name: "OpenAI",
|
|
3619
|
+
endpoint: "https://api.openai.com/v1/chat/completions",
|
|
3620
|
+
defaultModel: "gpt-4o"
|
|
3621
|
+
},
|
|
3622
|
+
claude: {
|
|
3623
|
+
name: "Claude",
|
|
3624
|
+
endpoint: "https://api.anthropic.com/v1/messages",
|
|
3625
|
+
defaultModel: "claude-3-5-sonnet-20241022"
|
|
3626
|
+
},
|
|
3627
|
+
ollama: {
|
|
3628
|
+
name: "Ollama",
|
|
3629
|
+
endpoint: "http://localhost:11434/api/generate",
|
|
3630
|
+
defaultModel: "qwen2.5-coder:14b"
|
|
3631
|
+
}
|
|
3632
|
+
};
|
|
3633
|
+
function getRecentCommits3(limit = 20) {
|
|
3634
|
+
try {
|
|
3635
|
+
const output = execOutput(
|
|
3636
|
+
`git log -${limit} --pretty=format:"%H|%h|%s|%an|%ad" --date=short`
|
|
3637
|
+
);
|
|
3638
|
+
if (!output) return [];
|
|
3639
|
+
return output.split("\n").filter(Boolean).map((line) => {
|
|
3640
|
+
const [hash, shortHash, subject, author, date] = line.split("|");
|
|
3641
|
+
return { hash, shortHash, subject, author, date };
|
|
3642
|
+
});
|
|
3643
|
+
} catch {
|
|
3644
|
+
return [];
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
function getStagedDiff() {
|
|
3648
|
+
try {
|
|
3649
|
+
const diff = execOutput("git diff --cached");
|
|
3650
|
+
if (diff) return diff;
|
|
3651
|
+
return execOutput("git diff") || "";
|
|
3652
|
+
} catch {
|
|
3653
|
+
return "";
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
function getCommitDiff(hash) {
|
|
3657
|
+
try {
|
|
3658
|
+
return execOutput(`git show ${hash} --format="" --patch`) || "";
|
|
3659
|
+
} catch {
|
|
3660
|
+
return "";
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
function getMultipleCommitsDiff(hashes) {
|
|
3664
|
+
if (hashes.length === 0) return "";
|
|
3665
|
+
if (hashes.length === 1) return getCommitDiff(hashes[0]);
|
|
3666
|
+
const oldest = hashes[hashes.length - 1];
|
|
3667
|
+
const newest = hashes[0];
|
|
3668
|
+
try {
|
|
3669
|
+
return execOutput(`git diff ${oldest}^..${newest}`) || "";
|
|
3670
|
+
} catch {
|
|
3671
|
+
return hashes.map((h) => getCommitDiff(h)).join("\n\n");
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
function parseDiff(diff) {
|
|
3675
|
+
const files = [];
|
|
3676
|
+
const fileDiffs = diff.split(/^diff --git /m).filter(Boolean);
|
|
3677
|
+
for (const fileDiff of fileDiffs) {
|
|
3678
|
+
const lines = fileDiff.split("\n");
|
|
3679
|
+
const headerMatch = lines[0]?.match(/a\/(.+) b\/(.+)/);
|
|
3680
|
+
if (!headerMatch) continue;
|
|
3681
|
+
const oldPath = headerMatch[1];
|
|
3682
|
+
const newPath = headerMatch[2];
|
|
3683
|
+
let status = "M";
|
|
3684
|
+
if (fileDiff.includes("new file mode")) status = "A";
|
|
3685
|
+
else if (fileDiff.includes("deleted file mode")) status = "D";
|
|
3686
|
+
else if (fileDiff.includes("rename from")) status = "R";
|
|
3687
|
+
files.push({
|
|
3688
|
+
oldPath,
|
|
3689
|
+
newPath,
|
|
3690
|
+
status,
|
|
3691
|
+
diff: "diff --git " + fileDiff
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
3694
|
+
return files;
|
|
3695
|
+
}
|
|
3696
|
+
function getDiffStats(diff) {
|
|
3697
|
+
const lines = diff.split("\n");
|
|
3698
|
+
let additions = 0;
|
|
3699
|
+
let deletions = 0;
|
|
3700
|
+
for (const line of lines) {
|
|
3701
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
3702
|
+
additions++;
|
|
3703
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
3704
|
+
deletions++;
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
const files = parseDiff(diff).length;
|
|
3708
|
+
return { additions, deletions, files };
|
|
3709
|
+
}
|
|
3710
|
+
function buildSystemPrompt(language) {
|
|
3711
|
+
const isZh = language === "zh-CN";
|
|
3712
|
+
if (isZh) {
|
|
3713
|
+
return `\u4F60\u662F\u4E00\u4E2A\u8D44\u6DF1\u7684\u4EE3\u7801\u5BA1\u67E5\u4E13\u5BB6\uFF0C\u62E5\u6709\u4E30\u5BCC\u7684\u8F6F\u4EF6\u5F00\u53D1\u7ECF\u9A8C\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u5BA1\u67E5 Git \u63D0\u4EA4\u4E2D\u7684\u4EE3\u7801\u53D8\u66F4\uFF0C\u63D0\u4F9B\u4E13\u4E1A\u3001\u6709\u4EF7\u503C\u3001\u6709\u5EFA\u8BBE\u6027\u7684\u5BA1\u67E5\u610F\u89C1\u3002
|
|
3714
|
+
|
|
3715
|
+
## \u5BA1\u67E5\u539F\u5219
|
|
3716
|
+
|
|
3717
|
+
1. **\u91CD\u70B9\u5173\u6CE8\u53D8\u66F4\u4EE3\u7801**\uFF1A\u53EA\u5BA1\u67E5 diff \u4E2D\u5E26 \`+\` \u6216 \`-\` \u7684\u4EE3\u7801\u884C\uFF0C\u8FD9\u4E9B\u662F\u5B9E\u9645\u7684\u53D8\u66F4\u5185\u5BB9
|
|
3718
|
+
2. **\u63D0\u4F9B\u5177\u4F53\u5EFA\u8BAE**\uFF1A\u4E0D\u8981\u6CDB\u6CDB\u800C\u8C08\uFF0C\u8981\u9488\u5BF9\u5177\u4F53\u4EE3\u7801\u884C\u7ED9\u51FA\u6539\u8FDB\u5EFA\u8BAE
|
|
3719
|
+
3. **\u533A\u5206\u95EE\u9898\u4E25\u91CD\u7A0B\u5EA6**\uFF1A\u4F7F\u7528 \u{1F534} \u4E25\u91CD\u3001\u{1F7E1} \u8B66\u544A\u3001\u{1F535} \u5EFA\u8BAE \u4E09\u4E2A\u7EA7\u522B
|
|
3720
|
+
4. **\u4EE3\u7801\u793A\u4F8B**\uFF1A\u5728\u5EFA\u8BAE\u4FEE\u6539\u65F6\uFF0C\u5C3D\u53EF\u80FD\u63D0\u4F9B\u4FEE\u6539\u540E\u7684\u4EE3\u7801\u793A\u4F8B
|
|
3721
|
+
5. **\u6B63\u9762\u53CD\u9988**\uFF1A\u5BF9\u4E8E\u5199\u5F97\u597D\u7684\u4EE3\u7801\uFF0C\u4E5F\u8981\u7ED9\u4E88\u80AF\u5B9A
|
|
3722
|
+
|
|
3723
|
+
## \u5BA1\u67E5\u7EF4\u5EA6
|
|
3724
|
+
|
|
3725
|
+
1. **\u4EE3\u7801\u8D28\u91CF**\uFF1A\u53EF\u8BFB\u6027\u3001\u53EF\u7EF4\u62A4\u6027\u3001\u4EE3\u7801\u98CE\u683C
|
|
3726
|
+
2. **\u6F5C\u5728 Bug**\uFF1A\u7A7A\u6307\u9488\u3001\u8FB9\u754C\u6761\u4EF6\u3001\u5F02\u5E38\u5904\u7406
|
|
3727
|
+
3. **\u5B89\u5168\u95EE\u9898**\uFF1ASQL \u6CE8\u5165\u3001XSS\u3001\u654F\u611F\u4FE1\u606F\u6CC4\u9732
|
|
3728
|
+
4. **\u6027\u80FD\u95EE\u9898**\uFF1A\u4E0D\u5FC5\u8981\u7684\u5FAA\u73AF\u3001\u5185\u5B58\u6CC4\u6F0F\u3001\u91CD\u590D\u8BA1\u7B97
|
|
3729
|
+
5. **\u6700\u4F73\u5B9E\u8DF5**\uFF1A\u8BBE\u8BA1\u6A21\u5F0F\u3001SOLID \u539F\u5219\u3001DRY \u539F\u5219
|
|
3730
|
+
|
|
3731
|
+
## Diff \u683C\u5F0F\u8BF4\u660E
|
|
3732
|
+
|
|
3733
|
+
- \u4EE5 \`+\` \u5F00\u5934\u7684\u884C\u662F\u65B0\u589E\u7684\u4EE3\u7801
|
|
3734
|
+
- \u4EE5 \`-\` \u5F00\u5934\u7684\u884C\u662F\u5220\u9664\u7684\u4EE3\u7801
|
|
3735
|
+
- \`@@\` \u884C\u8868\u793A\u4EE3\u7801\u4F4D\u7F6E\u4FE1\u606F\uFF0C\u683C\u5F0F\u4E3A \`@@ -\u65E7\u6587\u4EF6\u8D77\u59CB\u884C,\u884C\u6570 +\u65B0\u6587\u4EF6\u8D77\u59CB\u884C,\u884C\u6570 @@\`
|
|
3736
|
+
- \u6CA1\u6709 \`+\` \u6216 \`-\` \u524D\u7F00\u7684\u884C\u662F\u4E0A\u4E0B\u6587\u4EE3\u7801\uFF0C\u7528\u4E8E\u5E2E\u52A9\u7406\u89E3\u53D8\u66F4
|
|
3737
|
+
|
|
3738
|
+
## \u8F93\u51FA\u683C\u5F0F
|
|
3739
|
+
|
|
3740
|
+
\u8BF7\u4F7F\u7528 Markdown \u683C\u5F0F\u8F93\u51FA\u5BA1\u67E5\u62A5\u544A\uFF0C\u5305\u542B\u4EE5\u4E0B\u90E8\u5206\uFF1A
|
|
3741
|
+
|
|
3742
|
+
1. **\u6982\u8FF0**\uFF1A\u7B80\u8981\u603B\u7ED3\u672C\u6B21\u53D8\u66F4\u7684\u5185\u5BB9\u548C\u6574\u4F53\u8BC4\u4EF7
|
|
3743
|
+
2. **\u95EE\u9898\u5217\u8868**\uFF1A\u6309\u4E25\u91CD\u7A0B\u5EA6\u5217\u51FA\u53D1\u73B0\u7684\u95EE\u9898
|
|
3744
|
+
3. **\u6539\u8FDB\u5EFA\u8BAE**\uFF1A\u63D0\u4F9B\u5177\u4F53\u7684\u4EE3\u7801\u6539\u8FDB\u5EFA\u8BAE
|
|
3745
|
+
4. **\u4EAE\u70B9**\uFF1A\u6307\u51FA\u4EE3\u7801\u4E2D\u5199\u5F97\u597D\u7684\u5730\u65B9\uFF08\u5982\u679C\u6709\uFF09
|
|
3746
|
+
|
|
3747
|
+
\u6CE8\u610F\uFF1A
|
|
3748
|
+
- \u6BCF\u4E2A\u95EE\u9898\u90FD\u8981\u6307\u660E\u6587\u4EF6\u8DEF\u5F84\u548C\u884C\u53F7
|
|
3749
|
+
- \u63D0\u4F9B\u4FEE\u6539\u5EFA\u8BAE\u65F6\u8981\u7ED9\u51FA\u4EE3\u7801\u793A\u4F8B
|
|
3750
|
+
- \u5982\u679C\u4EE3\u7801\u6CA1\u6709\u660E\u663E\u95EE\u9898\uFF0C\u4E5F\u8981\u8BF4\u660E\u5BA1\u67E5\u7ED3\u8BBA`;
|
|
3751
|
+
}
|
|
3752
|
+
return `You are a senior code review expert with extensive software development experience. Your task is to review code changes in Git commits and provide professional, valuable, and constructive review feedback.
|
|
3753
|
+
|
|
3754
|
+
## Review Principles
|
|
3755
|
+
|
|
3756
|
+
1. **Focus on Changed Code**: Only review lines with \`+\` or \`-\` prefixes in the diff - these are the actual changes
|
|
3757
|
+
2. **Provide Specific Suggestions**: Don't be vague, give improvement suggestions for specific code lines
|
|
3758
|
+
3. **Categorize Issue Severity**: Use \u{1F534} Critical, \u{1F7E1} Warning, \u{1F535} Suggestion levels
|
|
3759
|
+
4. **Code Examples**: When suggesting changes, provide modified code examples whenever possible
|
|
3760
|
+
5. **Positive Feedback**: Also acknowledge well-written code
|
|
3761
|
+
|
|
3762
|
+
## Review Dimensions
|
|
3763
|
+
|
|
3764
|
+
1. **Code Quality**: Readability, maintainability, code style
|
|
3765
|
+
2. **Potential Bugs**: Null pointers, boundary conditions, exception handling
|
|
3766
|
+
3. **Security Issues**: SQL injection, XSS, sensitive data exposure
|
|
3767
|
+
4. **Performance Issues**: Unnecessary loops, memory leaks, redundant calculations
|
|
3768
|
+
5. **Best Practices**: Design patterns, SOLID principles, DRY principle
|
|
3769
|
+
|
|
3770
|
+
## Diff Format Explanation
|
|
3771
|
+
|
|
3772
|
+
- Lines starting with \`+\` are added code
|
|
3773
|
+
- Lines starting with \`-\` are deleted code
|
|
3774
|
+
- \`@@\` lines indicate code location, format: \`@@ -old_start,count +new_start,count @@\`
|
|
3775
|
+
- Lines without \`+\` or \`-\` prefix are context code to help understand changes
|
|
3776
|
+
|
|
3777
|
+
## Output Format
|
|
3778
|
+
|
|
3779
|
+
Please output the review report in Markdown format, including:
|
|
3780
|
+
|
|
3781
|
+
1. **Overview**: Brief summary of changes and overall assessment
|
|
3782
|
+
2. **Issues**: List issues by severity
|
|
3783
|
+
3. **Suggestions**: Provide specific code improvement suggestions
|
|
3784
|
+
4. **Highlights**: Point out well-written code (if any)
|
|
3785
|
+
|
|
3786
|
+
Note:
|
|
3787
|
+
- Each issue should specify file path and line number
|
|
3788
|
+
- Provide code examples when suggesting modifications
|
|
3789
|
+
- If no obvious issues, state the review conclusion`;
|
|
3790
|
+
}
|
|
3791
|
+
function buildUserPrompt(diff, commits, language) {
|
|
3792
|
+
const isZh = language === "zh-CN";
|
|
3793
|
+
const stats = getDiffStats(diff);
|
|
3794
|
+
const files = parseDiff(diff);
|
|
3795
|
+
let prompt = "";
|
|
3796
|
+
if (isZh) {
|
|
3797
|
+
prompt += `## \u53D8\u66F4\u6982\u89C8
|
|
3798
|
+
|
|
3799
|
+
`;
|
|
3800
|
+
prompt += `- \u6D89\u53CA\u6587\u4EF6: ${stats.files} \u4E2A
|
|
3801
|
+
`;
|
|
3802
|
+
prompt += `- \u65B0\u589E\u884C\u6570: +${stats.additions}
|
|
3803
|
+
`;
|
|
3804
|
+
prompt += `- \u5220\u9664\u884C\u6570: -${stats.deletions}
|
|
3805
|
+
|
|
3806
|
+
`;
|
|
3807
|
+
if (commits.length > 0) {
|
|
3808
|
+
prompt += `## \u76F8\u5173\u63D0\u4EA4
|
|
3809
|
+
|
|
3810
|
+
`;
|
|
3811
|
+
for (const commit2 of commits) {
|
|
3812
|
+
prompt += `- \`${commit2.shortHash}\` ${commit2.subject} (${commit2.author}, ${commit2.date})
|
|
3813
|
+
`;
|
|
3814
|
+
}
|
|
3815
|
+
prompt += `
|
|
3816
|
+
`;
|
|
3817
|
+
}
|
|
3818
|
+
prompt += `## \u53D8\u66F4\u6587\u4EF6\u5217\u8868
|
|
3819
|
+
|
|
3820
|
+
`;
|
|
3821
|
+
for (const file of files) {
|
|
3822
|
+
const statusIcon = file.status === "A" ? "\u{1F195}" : file.status === "D" ? "\u{1F5D1}\uFE0F" : file.status === "R" ? "\u{1F4DD}" : "\u270F\uFE0F";
|
|
3823
|
+
prompt += `- ${statusIcon} \`${file.newPath}\`
|
|
3824
|
+
`;
|
|
3825
|
+
}
|
|
3826
|
+
prompt += `
|
|
3827
|
+
`;
|
|
3828
|
+
prompt += `## Diff \u5185\u5BB9
|
|
3829
|
+
|
|
3830
|
+
\u8BF7\u4ED4\u7EC6\u5BA1\u67E5\u4EE5\u4E0B\u4EE3\u7801\u53D8\u66F4\uFF1A
|
|
3831
|
+
|
|
3832
|
+
`;
|
|
3833
|
+
} else {
|
|
3834
|
+
prompt += `## Change Overview
|
|
3835
|
+
|
|
3836
|
+
`;
|
|
3837
|
+
prompt += `- Files changed: ${stats.files}
|
|
3838
|
+
`;
|
|
3839
|
+
prompt += `- Lines added: +${stats.additions}
|
|
3840
|
+
`;
|
|
3841
|
+
prompt += `- Lines deleted: -${stats.deletions}
|
|
3842
|
+
|
|
3843
|
+
`;
|
|
3844
|
+
if (commits.length > 0) {
|
|
3845
|
+
prompt += `## Related Commits
|
|
3846
|
+
|
|
3847
|
+
`;
|
|
3848
|
+
for (const commit2 of commits) {
|
|
3849
|
+
prompt += `- \`${commit2.shortHash}\` ${commit2.subject} (${commit2.author}, ${commit2.date})
|
|
3850
|
+
`;
|
|
3851
|
+
}
|
|
3852
|
+
prompt += `
|
|
3853
|
+
`;
|
|
3854
|
+
}
|
|
3855
|
+
prompt += `## Changed Files
|
|
3856
|
+
|
|
3857
|
+
`;
|
|
3858
|
+
for (const file of files) {
|
|
3859
|
+
const statusIcon = file.status === "A" ? "\u{1F195}" : file.status === "D" ? "\u{1F5D1}\uFE0F" : file.status === "R" ? "\u{1F4DD}" : "\u270F\uFE0F";
|
|
3860
|
+
prompt += `- ${statusIcon} \`${file.newPath}\`
|
|
3861
|
+
`;
|
|
3862
|
+
}
|
|
3863
|
+
prompt += `
|
|
3864
|
+
`;
|
|
3865
|
+
prompt += `## Diff Content
|
|
3866
|
+
|
|
3867
|
+
Please carefully review the following code changes:
|
|
3868
|
+
|
|
3869
|
+
`;
|
|
3870
|
+
}
|
|
3871
|
+
for (const file of files) {
|
|
3872
|
+
prompt += `### ${file.newPath}
|
|
3873
|
+
|
|
3874
|
+
`;
|
|
3875
|
+
prompt += "```diff\n";
|
|
3876
|
+
prompt += file.diff;
|
|
3877
|
+
prompt += "\n```\n\n";
|
|
3878
|
+
}
|
|
3879
|
+
return prompt;
|
|
3880
|
+
}
|
|
3881
|
+
async function callGitHubAPI2(systemPrompt, userPrompt, apiKey, model) {
|
|
3882
|
+
const response = await fetch(AI_PROVIDERS2.github.endpoint, {
|
|
3883
|
+
method: "POST",
|
|
3884
|
+
headers: {
|
|
3885
|
+
Authorization: `Bearer ${apiKey}`,
|
|
3886
|
+
"Content-Type": "application/json"
|
|
3887
|
+
},
|
|
3888
|
+
body: JSON.stringify({
|
|
3889
|
+
model,
|
|
3890
|
+
messages: [
|
|
3891
|
+
{ role: "system", content: systemPrompt },
|
|
3892
|
+
{ role: "user", content: userPrompt }
|
|
3893
|
+
],
|
|
3894
|
+
max_tokens: 4e3,
|
|
3895
|
+
temperature: 0.3
|
|
3896
|
+
})
|
|
3897
|
+
});
|
|
3898
|
+
if (!response.ok) {
|
|
3899
|
+
const error = await response.text();
|
|
3900
|
+
throw new Error(`GitHub Models API \u9519\u8BEF: ${response.status} ${error}`);
|
|
3901
|
+
}
|
|
3902
|
+
const data = await response.json();
|
|
3903
|
+
return data.choices[0]?.message?.content?.trim() || "";
|
|
3904
|
+
}
|
|
3905
|
+
async function callOpenAIAPI2(systemPrompt, userPrompt, apiKey, model) {
|
|
3906
|
+
const response = await fetch(AI_PROVIDERS2.openai.endpoint, {
|
|
3907
|
+
method: "POST",
|
|
3908
|
+
headers: {
|
|
3909
|
+
Authorization: `Bearer ${apiKey}`,
|
|
3910
|
+
"Content-Type": "application/json"
|
|
3911
|
+
},
|
|
3912
|
+
body: JSON.stringify({
|
|
3913
|
+
model,
|
|
3914
|
+
messages: [
|
|
3915
|
+
{ role: "system", content: systemPrompt },
|
|
3916
|
+
{ role: "user", content: userPrompt }
|
|
3917
|
+
],
|
|
3918
|
+
max_tokens: 4e3,
|
|
3919
|
+
temperature: 0.3
|
|
3920
|
+
})
|
|
3921
|
+
});
|
|
3922
|
+
if (!response.ok) {
|
|
3923
|
+
const error = await response.text();
|
|
3924
|
+
throw new Error(`OpenAI API \u9519\u8BEF: ${response.status} ${error}`);
|
|
3925
|
+
}
|
|
3926
|
+
const data = await response.json();
|
|
3927
|
+
return data.choices[0]?.message?.content?.trim() || "";
|
|
3928
|
+
}
|
|
3929
|
+
async function callClaudeAPI2(systemPrompt, userPrompt, apiKey, model) {
|
|
3930
|
+
const response = await fetch(AI_PROVIDERS2.claude.endpoint, {
|
|
3931
|
+
method: "POST",
|
|
3932
|
+
headers: {
|
|
3933
|
+
"x-api-key": apiKey,
|
|
3934
|
+
"anthropic-version": "2023-06-01",
|
|
3935
|
+
"Content-Type": "application/json"
|
|
3936
|
+
},
|
|
3937
|
+
body: JSON.stringify({
|
|
3938
|
+
model,
|
|
3939
|
+
system: systemPrompt,
|
|
3940
|
+
messages: [{ role: "user", content: userPrompt }],
|
|
3941
|
+
max_tokens: 4e3,
|
|
3942
|
+
temperature: 0.3
|
|
3943
|
+
})
|
|
3944
|
+
});
|
|
3945
|
+
if (!response.ok) {
|
|
3946
|
+
const error = await response.text();
|
|
3947
|
+
throw new Error(`Claude API \u9519\u8BEF: ${response.status} ${error}`);
|
|
3948
|
+
}
|
|
3949
|
+
const data = await response.json();
|
|
3950
|
+
return data.content[0]?.text?.trim() || "";
|
|
3951
|
+
}
|
|
3952
|
+
async function callOllamaAPI2(systemPrompt, userPrompt, model) {
|
|
3953
|
+
try {
|
|
3954
|
+
const response = await fetch(AI_PROVIDERS2.ollama.endpoint, {
|
|
3955
|
+
method: "POST",
|
|
3956
|
+
headers: { "Content-Type": "application/json" },
|
|
3957
|
+
body: JSON.stringify({
|
|
3958
|
+
model,
|
|
3959
|
+
prompt: `${systemPrompt}
|
|
3960
|
+
|
|
3961
|
+
${userPrompt}`,
|
|
3962
|
+
stream: false,
|
|
3963
|
+
options: {
|
|
3964
|
+
num_predict: 4e3,
|
|
3965
|
+
temperature: 0.3
|
|
3966
|
+
}
|
|
3967
|
+
})
|
|
3968
|
+
});
|
|
3969
|
+
if (!response.ok) {
|
|
3970
|
+
throw new Error(`Ollama \u672A\u8FD0\u884C\u6216\u6A21\u578B\u672A\u5B89\u88C5`);
|
|
3971
|
+
}
|
|
3972
|
+
const data = await response.json();
|
|
3973
|
+
return data.response?.trim() || "";
|
|
3974
|
+
} catch (error) {
|
|
3975
|
+
throw new Error(
|
|
3976
|
+
`Ollama \u8FDE\u63A5\u5931\u8D25\u3002\u8BF7\u786E\u4FDD\uFF1A
|
|
3977
|
+
1. \u5DF2\u5B89\u88C5 Ollama (https://ollama.com)
|
|
3978
|
+
2. \u8FD0\u884C 'ollama serve'
|
|
3979
|
+
3. \u4E0B\u8F7D\u6A21\u578B 'ollama pull ${model}'`
|
|
3980
|
+
);
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
async function callAIReview(diff, commits, config2) {
|
|
3984
|
+
const aiConfig = config2.aiCommit || {};
|
|
3985
|
+
const provider = aiConfig.provider || "github";
|
|
3986
|
+
const language = aiConfig.language || "zh-CN";
|
|
3987
|
+
const apiKey = aiConfig.apiKey || "";
|
|
3988
|
+
const providerInfo = AI_PROVIDERS2[provider];
|
|
3989
|
+
if (!providerInfo) {
|
|
3990
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684 AI \u63D0\u4F9B\u5546: ${provider}`);
|
|
3991
|
+
}
|
|
3992
|
+
const model = aiConfig.model || providerInfo.defaultModel;
|
|
3993
|
+
if (provider !== "ollama" && !apiKey) {
|
|
3994
|
+
throw new Error(
|
|
3995
|
+
`${providerInfo.name} \u9700\u8981 API key\u3002\u8BF7\u8FD0\u884C 'gw init' \u914D\u7F6E\uFF0C\u6216\u5728 .gwrc.json \u4E2D\u8BBE\u7F6E aiCommit.apiKey`
|
|
3996
|
+
);
|
|
3997
|
+
}
|
|
3998
|
+
const systemPrompt = buildSystemPrompt(language);
|
|
3999
|
+
const userPrompt = buildUserPrompt(diff, commits, language);
|
|
4000
|
+
const maxLength = 3e4;
|
|
4001
|
+
const truncatedUserPrompt = userPrompt.length > maxLength ? userPrompt.slice(0, maxLength) + "\n\n[... diff \u5185\u5BB9\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD ...]" : userPrompt;
|
|
4002
|
+
switch (provider) {
|
|
4003
|
+
case "github":
|
|
4004
|
+
return callGitHubAPI2(systemPrompt, truncatedUserPrompt, apiKey, model);
|
|
4005
|
+
case "openai":
|
|
4006
|
+
return callOpenAIAPI2(systemPrompt, truncatedUserPrompt, apiKey, model);
|
|
4007
|
+
case "claude":
|
|
4008
|
+
return callClaudeAPI2(systemPrompt, truncatedUserPrompt, apiKey, model);
|
|
4009
|
+
case "ollama":
|
|
4010
|
+
return callOllamaAPI2(systemPrompt, truncatedUserPrompt, model);
|
|
4011
|
+
default:
|
|
4012
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684 AI \u63D0\u4F9B\u5546: ${provider}`);
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
function generateReportFile(reviewContent, commits, stats, outputPath) {
|
|
4016
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
4017
|
+
const commitInfo = commits.length > 0 ? commits.map((c) => c.shortHash).join("-") : "staged";
|
|
4018
|
+
const reviewDir = ".gw-reviews";
|
|
4019
|
+
if (!existsSync5(reviewDir)) {
|
|
4020
|
+
mkdirSync(reviewDir, { recursive: true });
|
|
4021
|
+
}
|
|
4022
|
+
const filename = outputPath || join6(reviewDir, `review-${commitInfo}-${timestamp}.md`);
|
|
4023
|
+
let report = `# \u{1F50D} \u4EE3\u7801\u5BA1\u67E5\u62A5\u544A
|
|
4024
|
+
|
|
4025
|
+
`;
|
|
4026
|
+
report += `> \u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}
|
|
4027
|
+
|
|
4028
|
+
`;
|
|
4029
|
+
report += `## \u{1F4CA} \u53D8\u66F4\u7EDF\u8BA1
|
|
4030
|
+
|
|
4031
|
+
`;
|
|
4032
|
+
report += `| \u6307\u6807 | \u6570\u503C |
|
|
4033
|
+
`;
|
|
4034
|
+
report += `|------|------|
|
|
4035
|
+
`;
|
|
4036
|
+
report += `| \u6587\u4EF6\u6570 | ${stats.files} |
|
|
4037
|
+
`;
|
|
4038
|
+
report += `| \u65B0\u589E\u884C | +${stats.additions} |
|
|
4039
|
+
`;
|
|
4040
|
+
report += `| \u5220\u9664\u884C | -${stats.deletions} |
|
|
4041
|
+
|
|
4042
|
+
`;
|
|
4043
|
+
if (commits.length > 0) {
|
|
4044
|
+
report += `## \u{1F4DD} \u5BA1\u67E5\u7684\u63D0\u4EA4
|
|
4045
|
+
|
|
4046
|
+
`;
|
|
4047
|
+
for (const commit2 of commits) {
|
|
4048
|
+
report += `- \`${commit2.shortHash}\` ${commit2.subject} - ${commit2.author} (${commit2.date})
|
|
4049
|
+
`;
|
|
4050
|
+
}
|
|
4051
|
+
report += `
|
|
4052
|
+
`;
|
|
4053
|
+
}
|
|
4054
|
+
report += `## \u{1F916} AI \u5BA1\u67E5\u7ED3\u679C
|
|
4055
|
+
|
|
4056
|
+
`;
|
|
4057
|
+
report += reviewContent;
|
|
4058
|
+
report += `
|
|
4059
|
+
|
|
4060
|
+
---
|
|
4061
|
+
|
|
4062
|
+
`;
|
|
4063
|
+
report += `*\u672C\u62A5\u544A\u7531 [git-workflow](https://github.com/iamzjt-front-end/git-workflow) \u7684 AI Review \u529F\u80FD\u751F\u6210*
|
|
4064
|
+
`;
|
|
4065
|
+
writeFileSync5(filename, report, "utf-8");
|
|
4066
|
+
return filename;
|
|
4067
|
+
}
|
|
4068
|
+
async function review(hashes, options = {}) {
|
|
4069
|
+
const config2 = loadConfig();
|
|
4070
|
+
const aiConfig = config2.aiCommit;
|
|
4071
|
+
if (!aiConfig?.apiKey && aiConfig?.provider !== "ollama") {
|
|
4072
|
+
console.log(colors.red("\u274C \u672A\u914D\u7F6E AI API Key"));
|
|
4073
|
+
console.log("");
|
|
4074
|
+
console.log(colors.dim(" \u8BF7\u5148\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u914D\u7F6E AI:"));
|
|
4075
|
+
console.log(colors.cyan(" gw init"));
|
|
4076
|
+
console.log("");
|
|
4077
|
+
return;
|
|
4078
|
+
}
|
|
4079
|
+
let diff = "";
|
|
4080
|
+
let commits = [];
|
|
4081
|
+
if (hashes && hashes.length > 0) {
|
|
4082
|
+
commits = hashes.map((hash) => {
|
|
4083
|
+
const info = execOutput(
|
|
4084
|
+
`git log -1 --pretty=format:"%H|%h|%s|%an|%ad" --date=short ${hash}`
|
|
4085
|
+
);
|
|
4086
|
+
if (!info) {
|
|
4087
|
+
console.log(colors.red(`\u274C \u627E\u4E0D\u5230 commit: ${hash}`));
|
|
4088
|
+
process.exit(1);
|
|
4089
|
+
}
|
|
4090
|
+
const [fullHash, shortHash, subject, author, date] = info.split("|");
|
|
4091
|
+
return { hash: fullHash, shortHash, subject, author, date };
|
|
4092
|
+
});
|
|
4093
|
+
diff = getMultipleCommitsDiff(hashes);
|
|
4094
|
+
} else if (options.last) {
|
|
4095
|
+
commits = getRecentCommits3(options.last);
|
|
4096
|
+
diff = getMultipleCommitsDiff(commits.map((c) => c.hash));
|
|
4097
|
+
} else if (options.staged) {
|
|
4098
|
+
diff = getStagedDiff();
|
|
4099
|
+
} else {
|
|
4100
|
+
const recentCommits = getRecentCommits3(20);
|
|
4101
|
+
const stagedDiff = getStagedDiff();
|
|
4102
|
+
const choices = [];
|
|
4103
|
+
if (stagedDiff) {
|
|
4104
|
+
choices.push({
|
|
4105
|
+
name: `\u{1F4E6} \u6682\u5B58\u533A\u7684\u66F4\u6539 (staged changes)`,
|
|
4106
|
+
value: "staged"
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
4109
|
+
choices.push(
|
|
4110
|
+
...recentCommits.map((c) => ({
|
|
4111
|
+
name: `${colors.yellow(c.shortHash)} ${c.subject} ${colors.dim(`- ${c.author} (${c.date})`)}`,
|
|
4112
|
+
value: c.hash
|
|
4113
|
+
}))
|
|
4114
|
+
);
|
|
4115
|
+
if (choices.length === 0) {
|
|
4116
|
+
console.log(colors.yellow("\u26A0\uFE0F \u6CA1\u6709\u53EF\u5BA1\u67E5\u7684\u5185\u5BB9"));
|
|
4117
|
+
return;
|
|
4118
|
+
}
|
|
4119
|
+
divider();
|
|
4120
|
+
const selected = await checkbox2({
|
|
4121
|
+
message: "\u9009\u62E9\u8981\u5BA1\u67E5\u7684\u5185\u5BB9 (\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4):",
|
|
4122
|
+
choices,
|
|
4123
|
+
pageSize: choices.length,
|
|
4124
|
+
// 显示所有选项,不滚动
|
|
4125
|
+
loop: false,
|
|
4126
|
+
// 到达边界时不循环
|
|
4127
|
+
theme
|
|
4128
|
+
});
|
|
4129
|
+
if (selected.length === 0) {
|
|
4130
|
+
console.log(colors.yellow("\u26A0\uFE0F \u672A\u9009\u62E9\u4EFB\u4F55\u5185\u5BB9"));
|
|
4131
|
+
return;
|
|
4132
|
+
}
|
|
4133
|
+
if (selected.includes("staged")) {
|
|
4134
|
+
diff = stagedDiff;
|
|
4135
|
+
} else {
|
|
4136
|
+
commits = recentCommits.filter((c) => selected.includes(c.hash));
|
|
4137
|
+
diff = getMultipleCommitsDiff(selected);
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
if (!diff) {
|
|
4141
|
+
console.log(colors.yellow("\u26A0\uFE0F \u6CA1\u6709\u68C0\u6D4B\u5230\u4EE3\u7801\u53D8\u66F4"));
|
|
4142
|
+
return;
|
|
4143
|
+
}
|
|
4144
|
+
const stats = getDiffStats(diff);
|
|
4145
|
+
divider();
|
|
4146
|
+
console.log(colors.cyan("\u{1F4CA} \u53D8\u66F4\u7EDF\u8BA1:"));
|
|
4147
|
+
console.log(colors.dim(` \u6587\u4EF6: ${stats.files} \u4E2A`));
|
|
4148
|
+
console.log(colors.dim(` \u65B0\u589E: +${stats.additions} \u884C`));
|
|
4149
|
+
console.log(colors.dim(` \u5220\u9664: -${stats.deletions} \u884C`));
|
|
4150
|
+
divider();
|
|
4151
|
+
const spinner = ora7("\u{1F916} AI \u6B63\u5728\u5BA1\u67E5\u4EE3\u7801...").start();
|
|
4152
|
+
try {
|
|
4153
|
+
const reviewContent = await callAIReview(diff, commits, config2);
|
|
4154
|
+
spinner.succeed("AI \u5BA1\u67E5\u5B8C\u6210");
|
|
4155
|
+
const reportPath = generateReportFile(
|
|
4156
|
+
reviewContent,
|
|
4157
|
+
commits,
|
|
4158
|
+
stats,
|
|
4159
|
+
options.output
|
|
4160
|
+
);
|
|
4161
|
+
console.log("");
|
|
4162
|
+
console.log(colors.green(`\u2705 \u5BA1\u67E5\u62A5\u544A\u5DF2\u751F\u6210: ${colors.cyan(reportPath)}`));
|
|
4163
|
+
console.log("");
|
|
4164
|
+
const shouldOpen = await select10({
|
|
4165
|
+
message: "\u662F\u5426\u6253\u5F00\u5BA1\u67E5\u62A5\u544A?",
|
|
4166
|
+
choices: [
|
|
4167
|
+
{ name: "\u662F\uFF0C\u5728\u7F16\u8F91\u5668\u4E2D\u6253\u5F00", value: true },
|
|
4168
|
+
{ name: "\u5426\uFF0C\u7A0D\u540E\u67E5\u770B", value: false }
|
|
4169
|
+
],
|
|
4170
|
+
theme
|
|
4171
|
+
});
|
|
4172
|
+
if (shouldOpen) {
|
|
4173
|
+
try {
|
|
4174
|
+
const { exec: exec2 } = await import("child_process");
|
|
4175
|
+
exec2(`open "${reportPath}"`);
|
|
4176
|
+
} catch {
|
|
4177
|
+
console.log(colors.dim(` \u8BF7\u624B\u52A8\u6253\u5F00: ${reportPath}`));
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
} catch (error) {
|
|
4181
|
+
spinner.fail("AI \u5BA1\u67E5\u5931\u8D25");
|
|
4182
|
+
console.log("");
|
|
4183
|
+
console.log(colors.red(`\u274C ${error.message}`));
|
|
4184
|
+
console.log("");
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
|
|
3564
4188
|
// src/index.ts
|
|
3565
4189
|
process.on("uncaughtException", (err) => {
|
|
3566
4190
|
if (err instanceof ExitPromptError) {
|
|
@@ -3586,7 +4210,7 @@ process.on("SIGTERM", () => {
|
|
|
3586
4210
|
console.log("");
|
|
3587
4211
|
process.exit(0);
|
|
3588
4212
|
});
|
|
3589
|
-
var version = true ? "0.
|
|
4213
|
+
var version = true ? "0.5.0" : "0.0.0-dev";
|
|
3590
4214
|
async function mainMenu() {
|
|
3591
4215
|
console.log(
|
|
3592
4216
|
colors.green(`
|
|
@@ -3600,7 +4224,7 @@ async function mainMenu() {
|
|
|
3600
4224
|
);
|
|
3601
4225
|
console.log(colors.dim(` git-workflow v${colors.yellow(version)}
|
|
3602
4226
|
`));
|
|
3603
|
-
const action = await
|
|
4227
|
+
const action = await select11({
|
|
3604
4228
|
message: "\u9009\u62E9\u64CD\u4F5C:",
|
|
3605
4229
|
choices: [
|
|
3606
4230
|
{
|
|
@@ -3656,7 +4280,11 @@ async function mainMenu() {
|
|
|
3656
4280
|
value: "amend"
|
|
3657
4281
|
},
|
|
3658
4282
|
{
|
|
3659
|
-
name: `[e] \
|
|
4283
|
+
name: `[e] \u{1F50D} AI \u4EE3\u7801\u5BA1\u67E5 ${colors.dim("gw review")}`,
|
|
4284
|
+
value: "review"
|
|
4285
|
+
},
|
|
4286
|
+
{
|
|
4287
|
+
name: `[f] \u2699\uFE0F \u521D\u59CB\u5316\u914D\u7F6E ${colors.dim("gw init")}`,
|
|
3660
4288
|
value: "init"
|
|
3661
4289
|
},
|
|
3662
4290
|
{ name: "[0] \u2753 \u5E2E\u52A9", value: "help" },
|
|
@@ -3717,6 +4345,10 @@ async function mainMenu() {
|
|
|
3717
4345
|
checkGitRepo();
|
|
3718
4346
|
await amend();
|
|
3719
4347
|
break;
|
|
4348
|
+
case "review":
|
|
4349
|
+
checkGitRepo();
|
|
4350
|
+
await review();
|
|
4351
|
+
break;
|
|
3720
4352
|
case "init":
|
|
3721
4353
|
await init();
|
|
3722
4354
|
break;
|
|
@@ -3810,18 +4442,30 @@ cli.command("amend [hash]", "\u4FEE\u6539\u6307\u5B9A commit \u7684\u63D0\u4EA4\
|
|
|
3810
4442
|
checkGitRepo();
|
|
3811
4443
|
return amend(hash);
|
|
3812
4444
|
});
|
|
4445
|
+
cli.command("review [...hashes]", "AI \u4EE3\u7801\u5BA1\u67E5").alias("rw").option("-n, --last <number>", "\u5BA1\u67E5\u6700\u8FD1 N \u4E2A commits").option("-s, --staged", "\u5BA1\u67E5\u6682\u5B58\u533A\u7684\u66F4\u6539").option("-o, --output <path>", "\u6307\u5B9A\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84").action(async (hashes, options) => {
|
|
4446
|
+
await checkForUpdates(version, "@zjex/git-workflow");
|
|
4447
|
+
checkGitRepo();
|
|
4448
|
+
return review(
|
|
4449
|
+
hashes.length > 0 ? hashes : void 0,
|
|
4450
|
+
{
|
|
4451
|
+
last: options.last ? parseInt(options.last) : void 0,
|
|
4452
|
+
staged: options.staged,
|
|
4453
|
+
output: options.output
|
|
4454
|
+
}
|
|
4455
|
+
);
|
|
4456
|
+
});
|
|
3813
4457
|
cli.command("clean", "\u6E05\u7406\u7F13\u5B58\u548C\u4E34\u65F6\u6587\u4EF6").alias("cc").action(async () => {
|
|
3814
4458
|
const { clearUpdateCache: clearUpdateCache3 } = await Promise.resolve().then(() => (init_update_notifier(), update_notifier_exports));
|
|
3815
|
-
const { existsSync:
|
|
4459
|
+
const { existsSync: existsSync6, unlinkSync: unlinkSync4, readdirSync } = await import("fs");
|
|
3816
4460
|
const { homedir: homedir5, tmpdir: tmpdir2 } = await import("os");
|
|
3817
|
-
const { join:
|
|
3818
|
-
const { select:
|
|
4461
|
+
const { join: join7 } = await import("path");
|
|
4462
|
+
const { select: select12 } = await import("@inquirer/prompts");
|
|
3819
4463
|
let cleanedCount = 0;
|
|
3820
4464
|
let deletedGlobalConfig = false;
|
|
3821
|
-
const globalConfig =
|
|
3822
|
-
const hasGlobalConfig =
|
|
4465
|
+
const globalConfig = join7(homedir5(), ".gwrc.json");
|
|
4466
|
+
const hasGlobalConfig = existsSync6(globalConfig);
|
|
3823
4467
|
if (hasGlobalConfig) {
|
|
3824
|
-
const shouldDeleteConfig = await
|
|
4468
|
+
const shouldDeleteConfig = await select12({
|
|
3825
4469
|
message: "\u68C0\u6D4B\u5230\u5168\u5C40\u914D\u7F6E\u6587\u4EF6\uFF0C\u662F\u5426\u5220\u9664\uFF1F",
|
|
3826
4470
|
choices: [
|
|
3827
4471
|
{ name: "\u5426\uFF0C\u4FDD\u7559\u914D\u7F6E\u6587\u4EF6", value: false },
|
|
@@ -3846,7 +4490,7 @@ cli.command("clean", "\u6E05\u7406\u7F13\u5B58\u548C\u4E34\u65F6\u6587\u4EF6").a
|
|
|
3846
4490
|
const gwTmpFiles = files.filter((f) => f.startsWith(".gw-commit-msg-"));
|
|
3847
4491
|
for (const file of gwTmpFiles) {
|
|
3848
4492
|
try {
|
|
3849
|
-
unlinkSync4(
|
|
4493
|
+
unlinkSync4(join7(tmpDir, file));
|
|
3850
4494
|
cleanedCount++;
|
|
3851
4495
|
} catch {
|
|
3852
4496
|
}
|