@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +136 -377
  2. package/README.md +44 -6
  3. package/dist/index.js +675 -31
  4. package/docs/.vitepress/cache/deps/_metadata.json +10 -10
  5. package/docs/.vitepress/config.ts +4 -0
  6. package/docs/commands/index.md +4 -0
  7. package/docs/commands/review.md +142 -0
  8. package/docs/guide/ai-review.md +159 -0
  9. package/docs/guide/index.md +2 -0
  10. package/docs/index.md +26 -3
  11. package/package.json +1 -1
  12. package/scripts/generate-changelog-manual.js +15 -64
  13. package/src/commands/review.ts +759 -0
  14. package/src/commands/tag.ts +42 -10
  15. package/src/commands/update.ts +25 -9
  16. package/src/index.ts +29 -1
  17. package/src/utils.ts +3 -1
  18. package/tests/review.test.ts +1058 -0
  19. package/tests/update.test.ts +85 -69
  20. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-2CLQ7TTZ.js +0 -9719
  21. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-2CLQ7TTZ.js.map +0 -7
  22. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-LE5NDSFD.js +0 -12824
  23. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-LE5NDSFD.js.map +0 -7
  24. package/docs/.vitepress/cache/deps_temp_44e2fb0f/package.json +0 -3
  25. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vue_devtools-api.js +0 -4505
  26. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vue_devtools-api.js.map +0 -7
  27. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_core.js +0 -583
  28. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_core.js.map +0 -7
  29. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_integrations_useFocusTrap.js +0 -1352
  30. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_integrations_useFocusTrap.js.map +0 -7
  31. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___mark__js_src_vanilla__js.js +0 -1665
  32. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___mark__js_src_vanilla__js.js.map +0 -7
  33. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___minisearch.js +0 -1813
  34. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___minisearch.js.map +0 -7
  35. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vue.js +0 -347
  36. 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
- console.log(colors.dim(`[DEBUG] \u9519\u8BEF\u8F93\u51FA:
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 select10 } from "@inquirer/prompts";
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 deleteSuccess = await execAsync(`git tag -d "${oldTag}"`, spinner);
1210
- if (!deleteSuccess) {
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 pushNewSuccess = await execAsync(
1248
+ const pushNewResult = await execAsync(
1226
1249
  `git push origin "${newTag}"`,
1227
1250
  pushSpinner
1228
1251
  );
1229
- if (!pushNewSuccess) {
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 deleteOldSuccess = await execAsync(
1263
+ const deleteOldResult = await execAsync(
1238
1264
  `git push origin --delete "${oldTag}"`,
1239
1265
  pushSpinner
1240
1266
  );
1241
- if (!deleteOldSuccess) {
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 success = await execAsync(`git tag -d "${tag}"`, localSpinner);
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 success = await execAsync(
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
- try {
2810
- const result = execSync4(`npm view ${packageName} version`, {
2811
- encoding: "utf-8",
2812
- timeout: 3e3,
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
- return result.trim();
2816
- } catch {
2817
- return null;
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.4.6" : "0.0.0-dev";
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 select10({
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] \u2699\uFE0F \u521D\u59CB\u5316\u914D\u7F6E ${colors.dim("gw init")}`,
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: existsSync5, unlinkSync: unlinkSync4, readdirSync } = await import("fs");
4459
+ const { existsSync: existsSync6, unlinkSync: unlinkSync4, readdirSync } = await import("fs");
3816
4460
  const { homedir: homedir5, tmpdir: tmpdir2 } = await import("os");
3817
- const { join: join6 } = await import("path");
3818
- const { select: select11 } = await import("@inquirer/prompts");
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 = join6(homedir5(), ".gwrc.json");
3822
- const hasGlobalConfig = existsSync5(globalConfig);
4465
+ const globalConfig = join7(homedir5(), ".gwrc.json");
4466
+ const hasGlobalConfig = existsSync6(globalConfig);
3823
4467
  if (hasGlobalConfig) {
3824
- const shouldDeleteConfig = await select11({
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(join6(tmpDir, file));
4493
+ unlinkSync4(join7(tmpDir, file));
3850
4494
  cleanedCount++;
3851
4495
  } catch {
3852
4496
  }