ai-project-manage-cli 6.0.105 → 6.0.107

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 (2) hide show
  1. package/dist/index.js +508 -355
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -535,26 +535,194 @@ async function tryReadGitOriginUrl(cwd) {
535
535
  }
536
536
  }
537
537
 
538
+ // src/git-utils.ts
539
+ import { execFile as execFile2 } from "child_process";
540
+ import { promisify as promisify2 } from "util";
541
+ var execFileAsync2 = promisify2(execFile2);
542
+ async function execGit(cwd, args, quiet = false) {
543
+ try {
544
+ const { stdout, stderr } = await execFileAsync2("git", args, {
545
+ cwd,
546
+ encoding: "utf8",
547
+ maxBuffer: 10 * 1024 * 1024
548
+ });
549
+ if (!quiet && stderr.trim()) {
550
+ process.stderr.write(stderr);
551
+ }
552
+ return stdout;
553
+ } catch (err) {
554
+ const e = err;
555
+ const detail = (e.stderr ?? e.message ?? String(err)).trim();
556
+ throw new Error(
557
+ `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
558
+ );
559
+ }
560
+ }
561
+ async function isGitRepo(cwd) {
562
+ try {
563
+ await execGit(cwd, ["rev-parse", "--git-dir"], true);
564
+ return true;
565
+ } catch {
566
+ return false;
567
+ }
568
+ }
569
+ async function ensureGitRepo(cwd) {
570
+ await execGit(cwd, ["rev-parse", "--git-dir"], true);
571
+ }
572
+ async function getCurrentBranch(cwd) {
573
+ return (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
574
+ }
575
+ async function isWorkingTreeDirty(cwd) {
576
+ const out = await execGit(cwd, ["status", "--porcelain"], true);
577
+ return out.trim().length > 0;
578
+ }
579
+ async function remoteBranchExists(cwd, branch) {
580
+ const out = await execGit(
581
+ cwd,
582
+ ["ls-remote", "--heads", "origin", branch],
583
+ true
584
+ );
585
+ return out.trim().length > 0;
586
+ }
587
+ async function resolveGitRepoRoot(cwd) {
588
+ return (await execGit(cwd, ["rev-parse", "--show-toplevel"], true)).trim();
589
+ }
590
+ async function ensureRemoteBaselineBranch(cwd, baselineBranch) {
591
+ await execGit(cwd, ["fetch", "origin", baselineBranch], true);
592
+ if (await remoteBranchExists(cwd, baselineBranch)) {
593
+ return;
594
+ }
595
+ throw new Error(
596
+ `[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F origin/${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
597
+ );
598
+ }
599
+ async function hasUpstream(cwd) {
600
+ try {
601
+ await execGit(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], true);
602
+ return true;
603
+ } catch {
604
+ return false;
605
+ }
606
+ }
607
+ var GITIGNORE_COMMIT_MESSAGE = "chore(apm): ignore .apm directory";
608
+ async function commitAndPushGitignore(workdir) {
609
+ if (!await isGitRepo(workdir)) {
610
+ console.log("[apm] \u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8BF7\u624B\u52A8\u63D0\u4EA4 .gitignore");
611
+ return;
612
+ }
613
+ await execGit(workdir, ["add", "--", ".gitignore"]);
614
+ await execGit(workdir, ["commit", "-m", GITIGNORE_COMMIT_MESSAGE]);
615
+ console.log(`[apm] \u5DF2\u63D0\u4EA4 .gitignore: ${GITIGNORE_COMMIT_MESSAGE}`);
616
+ const originUrl = await tryReadGitOriginUrl(workdir);
617
+ if (!originUrl) {
618
+ console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
619
+ return;
620
+ }
621
+ if (await hasUpstream(workdir)) {
622
+ await execGit(workdir, ["push"]);
623
+ } else {
624
+ await execGit(workdir, ["push", "-u", "origin", "HEAD"]);
625
+ }
626
+ console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
627
+ }
628
+
629
+ // src/baseline-resolve.ts
630
+ function formatBaselineDiagnostic(workdirPath, baselineWorkdirPath, diagnostic) {
631
+ return diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baselineWorkdirPath}\uFF09
632
+ \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
633
+ }
634
+ async function matchRepositoryByGitRemote(api, workdirPath) {
635
+ const gitRoot = await resolveGitRepoRoot(workdirPath);
636
+ const gitUrl = await tryReadGitOriginUrl(gitRoot);
637
+ if (!gitUrl) {
638
+ return null;
639
+ }
640
+ const matched = await api.cli.matchRepository({ url: gitUrl });
641
+ const repositoryId = matched.repositoryId?.trim();
642
+ const defaultBranch = matched.defaultBranch?.trim();
643
+ if (!repositoryId || !defaultBranch) {
644
+ return null;
645
+ }
646
+ console.log(
647
+ `[apm] \u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u672A\u5339\u914D\uFF0C\u5DF2\u901A\u8FC7 git remote \u5173\u8054\u4ED3\u5E93: ${gitUrl}`
648
+ );
649
+ return { repositoryId, defaultBranch };
650
+ }
651
+ async function resolveWorkspaceBaseline(api, workdirPath) {
652
+ const baseline = await api.cli.workspaceBaseline({ workdirPath });
653
+ const repositoryId = baseline.repositoryId?.trim();
654
+ const defaultBranch = baseline.defaultBranch?.trim();
655
+ if (repositoryId && defaultBranch) {
656
+ return {
657
+ repositoryId,
658
+ defaultBranch,
659
+ workdirPath: baseline.workdirPath,
660
+ matchedViaGitRemote: false
661
+ };
662
+ }
663
+ const viaGit = await matchRepositoryByGitRemote(api, workdirPath);
664
+ if (viaGit) {
665
+ return {
666
+ ...viaGit,
667
+ workdirPath: baseline.workdirPath,
668
+ matchedViaGitRemote: true
669
+ };
670
+ }
671
+ throw new Error(
672
+ `[apm] ${formatBaselineDiagnostic(
673
+ workdirPath,
674
+ baseline.workdirPath,
675
+ baseline.diagnostic
676
+ )}`
677
+ );
678
+ }
679
+ async function resolveBranchBaseline(api, sessionId, workdirPath) {
680
+ const baseline = await api.cli.branchBaseline({ sessionId, workdirPath });
681
+ const repositoryId = baseline.repositoryId?.trim();
682
+ const defaultBranch = baseline.defaultBranch?.trim();
683
+ if (repositoryId && defaultBranch) {
684
+ return {
685
+ sessionId: baseline.sessionId,
686
+ taskId: baseline.taskId,
687
+ repositoryId,
688
+ defaultBranch,
689
+ workdirPath: baseline.workdirPath,
690
+ matchedViaGitRemote: false
691
+ };
692
+ }
693
+ const viaGit = await matchRepositoryByGitRemote(api, workdirPath);
694
+ if (viaGit) {
695
+ return {
696
+ sessionId: baseline.sessionId,
697
+ taskId: baseline.taskId,
698
+ ...viaGit,
699
+ workdirPath: baseline.workdirPath,
700
+ matchedViaGitRemote: true
701
+ };
702
+ }
703
+ throw new Error(
704
+ `[apm] ${formatBaselineDiagnostic(
705
+ workdirPath,
706
+ baseline.workdirPath,
707
+ baseline.diagnostic
708
+ )}`
709
+ );
710
+ }
711
+
538
712
  // src/deployment-config-sync.ts
539
713
  var TEMPLATE_HINT = "\u4FDD\u7559\u6A21\u677F .apm/apm.config.json";
540
714
  var SYNC_HINT = "\u767B\u8BB0\u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u3001\u7ED1\u5B9A\u4ED3\u5E93\u540E\uFF0C\u53EF\u6267\u884C: apm sync-deploy-config";
541
715
  async function resolveRepositoryIdForSync(api, workdirPath) {
542
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
543
- if (baseline.repositoryId) {
716
+ try {
717
+ const baseline = await resolveWorkspaceBaseline(api, workdirPath);
544
718
  return { repositoryId: baseline.repositoryId, diagnostic: null };
719
+ } catch (err) {
720
+ const detail = err instanceof Error ? err.message : String(err);
721
+ return {
722
+ repositoryId: null,
723
+ diagnostic: detail.replace(/^\[apm\]\s*/, "")
724
+ };
545
725
  }
546
- const gitUrl = await tryReadGitOriginUrl(workdirPath);
547
- if (gitUrl) {
548
- const matched = await api.cli.matchRepository({ url: gitUrl });
549
- if (matched.repositoryId) {
550
- console.log(
551
- `[apm] \u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u672A\u5339\u914D\uFF0C\u5DF2\u901A\u8FC7 git remote \u5173\u8054\u4ED3\u5E93: ${gitUrl}`
552
- );
553
- return { repositoryId: matched.repositoryId, diagnostic: null };
554
- }
555
- }
556
- const detail = baseline.diagnostic?.message ?? `\u5F53\u524D\u8DEF\u5F84\uFF08\u89C4\u8303\u5316\uFF1A${baseline.workdirPath}\uFF09\u672A\u5339\u914D\u5230\u5DF2\u7ED1\u5B9A\u4ED3\u5E93\u7684\u5DE5\u4F5C\u7A7A\u95F4\u3002`;
557
- return { repositoryId: null, diagnostic: detail };
558
726
  }
559
727
  async function syncRemoteDeploymentConfig(workdirPath, apmDir) {
560
728
  const cfg = await tryReadApmConfig();
@@ -816,85 +984,6 @@ async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
816
984
  return synced;
817
985
  }
818
986
 
819
- // src/git-utils.ts
820
- import { execFile as execFile2 } from "child_process";
821
- import { promisify as promisify2 } from "util";
822
- var execFileAsync2 = promisify2(execFile2);
823
- async function execGit(cwd, args, quiet = false) {
824
- try {
825
- const { stdout, stderr } = await execFileAsync2("git", args, {
826
- cwd,
827
- encoding: "utf8",
828
- maxBuffer: 10 * 1024 * 1024
829
- });
830
- if (!quiet && stderr.trim()) {
831
- process.stderr.write(stderr);
832
- }
833
- return stdout;
834
- } catch (err) {
835
- const e = err;
836
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
837
- throw new Error(
838
- `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
839
- );
840
- }
841
- }
842
- async function isGitRepo(cwd) {
843
- try {
844
- await execGit(cwd, ["rev-parse", "--git-dir"], true);
845
- return true;
846
- } catch {
847
- return false;
848
- }
849
- }
850
- async function ensureGitRepo(cwd) {
851
- await execGit(cwd, ["rev-parse", "--git-dir"], true);
852
- }
853
- async function getCurrentBranch(cwd) {
854
- return (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
855
- }
856
- async function isWorkingTreeDirty(cwd) {
857
- const out = await execGit(cwd, ["status", "--porcelain"], true);
858
- return out.trim().length > 0;
859
- }
860
- async function remoteBranchExists(cwd, branch) {
861
- const out = await execGit(
862
- cwd,
863
- ["ls-remote", "--heads", "origin", branch],
864
- true
865
- );
866
- return out.trim().length > 0;
867
- }
868
- async function hasUpstream(cwd) {
869
- try {
870
- await execGit(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], true);
871
- return true;
872
- } catch {
873
- return false;
874
- }
875
- }
876
- var GITIGNORE_COMMIT_MESSAGE = "chore(apm): ignore .apm directory";
877
- async function commitAndPushGitignore(workdir) {
878
- if (!await isGitRepo(workdir)) {
879
- console.log("[apm] \u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8BF7\u624B\u52A8\u63D0\u4EA4 .gitignore");
880
- return;
881
- }
882
- await execGit(workdir, ["add", "--", ".gitignore"]);
883
- await execGit(workdir, ["commit", "-m", GITIGNORE_COMMIT_MESSAGE]);
884
- console.log(`[apm] \u5DF2\u63D0\u4EA4 .gitignore: ${GITIGNORE_COMMIT_MESSAGE}`);
885
- const originUrl = await tryReadGitOriginUrl(workdir);
886
- if (!originUrl) {
887
- console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
888
- return;
889
- }
890
- if (await hasUpstream(workdir)) {
891
- await execGit(workdir, ["push"]);
892
- } else {
893
- await execGit(workdir, ["push", "-u", "origin", "HEAD"]);
894
- }
895
- console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
896
- }
897
-
898
987
  // src/commands/init.ts
899
988
  async function ensureWorkspaceInitialized(workdir, options) {
900
989
  if (isWorkspaceApmInitialized(workdir)) {
@@ -1010,9 +1099,6 @@ async function runLogin(opts) {
1010
1099
  }
1011
1100
 
1012
1101
  // src/commands/branch.ts
1013
- import { execFile as execFile3 } from "child_process";
1014
- import { promisify as promisify3 } from "util";
1015
- var execFileAsync3 = promisify3(execFile3);
1016
1102
  var SESSION_BRANCH_PREFIX = "feat/session-";
1017
1103
  function branchNameForSession(sessionId) {
1018
1104
  const id = sessionId.trim();
@@ -1034,47 +1120,9 @@ function sessionIdFromBranchName(branch) {
1034
1120
  const sessionId = name.slice(SESSION_BRANCH_PREFIX.length).trim();
1035
1121
  return sessionId || null;
1036
1122
  }
1037
- async function execGit2(cwd, args, quiet) {
1038
- try {
1039
- const { stdout, stderr } = await execFileAsync3("git", args, {
1040
- cwd,
1041
- encoding: "utf8",
1042
- maxBuffer: 10 * 1024 * 1024
1043
- });
1044
- if (!quiet && stderr.trim()) {
1045
- process.stderr.write(stderr);
1046
- }
1047
- return stdout;
1048
- } catch (err) {
1049
- const e = err;
1050
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
1051
- throw new Error(
1052
- `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
1053
- );
1054
- }
1055
- }
1056
- async function ensureGitRepo2(cwd) {
1057
- await execGit2(cwd, ["rev-parse", "--git-dir"], true);
1058
- }
1059
- async function getCurrentBranch2(cwd) {
1060
- const name = (await execGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1061
- return name;
1062
- }
1063
- async function isWorkingTreeDirty2(cwd) {
1064
- const out = await execGit2(cwd, ["status", "--porcelain"], true);
1065
- return out.trim().length > 0;
1066
- }
1067
- async function remoteHeadBranchExists(cwd, branch) {
1068
- const out = await execGit2(
1069
- cwd,
1070
- ["ls-remote", "--heads", "origin", branch],
1071
- true
1072
- );
1073
- return out.trim().length > 0;
1074
- }
1075
1123
  async function localBranchExists(cwd, branch) {
1076
1124
  try {
1077
- await execGit2(
1125
+ await execGit(
1078
1126
  cwd,
1079
1127
  ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
1080
1128
  true
@@ -1085,32 +1133,29 @@ async function localBranchExists(cwd, branch) {
1085
1133
  }
1086
1134
  }
1087
1135
  async function commitWorkingTreeIfDirty(cwd, message) {
1088
- await ensureGitRepo2(cwd);
1089
- if (!await isWorkingTreeDirty2(cwd)) {
1136
+ await ensureGitRepo(cwd);
1137
+ if (!await isWorkingTreeDirty(cwd)) {
1090
1138
  return false;
1091
1139
  }
1092
- const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch2(cwd)})`;
1093
- await execGit2(cwd, ["add", "-A"]);
1094
- await execGit2(cwd, ["commit", "-m", commitMessage]);
1140
+ const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
1141
+ await execGit(cwd, ["add", "-A"]);
1142
+ await execGit(cwd, ["commit", "-m", commitMessage]);
1095
1143
  console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
1096
1144
  return true;
1097
1145
  }
1098
1146
  async function ensureFeatureBranch(branch, baselineBranch, options) {
1099
1147
  const cwd = options.cwd ?? process.cwd();
1148
+ const gitRoot = await resolveGitRepoRoot(cwd);
1100
1149
  const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
1101
- await ensureGitRepo2(cwd);
1102
- if (!await remoteHeadBranchExists(cwd, baselineBranch)) {
1103
- throw new Error(
1104
- `[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F ${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
1105
- );
1106
- }
1107
- const current = await getCurrentBranch2(cwd);
1108
- const dirty = await isWorkingTreeDirty2(cwd);
1150
+ await ensureGitRepo(gitRoot);
1151
+ await ensureRemoteBaselineBranch(gitRoot, baselineBranch);
1152
+ const current = await getCurrentBranch(gitRoot);
1153
+ const dirty = await isWorkingTreeDirty(gitRoot);
1109
1154
  if (dirty) {
1110
1155
  if (current === branch) {
1111
- await commitWorkingTreeIfDirty(cwd, commitMessage);
1156
+ await commitWorkingTreeIfDirty(gitRoot, commitMessage);
1112
1157
  } else {
1113
- await execGit2(cwd, [
1158
+ await execGit(gitRoot, [
1114
1159
  "stash",
1115
1160
  "push",
1116
1161
  "-u",
@@ -1119,34 +1164,34 @@ async function ensureFeatureBranch(branch, baselineBranch, options) {
1119
1164
  ]);
1120
1165
  }
1121
1166
  }
1122
- const onTargetBranch = await getCurrentBranch2(cwd) === branch;
1167
+ const onTargetBranch = await getCurrentBranch(gitRoot) === branch;
1123
1168
  if (onTargetBranch) {
1124
- await execGit2(cwd, ["fetch", "origin", baselineBranch]);
1125
- await execGit2(cwd, ["merge", `origin/${baselineBranch}`, "--no-edit"]);
1169
+ await execGit(gitRoot, ["fetch", "origin", baselineBranch]);
1170
+ await execGit(gitRoot, ["merge", `origin/${baselineBranch}`, "--no-edit"]);
1126
1171
  } else {
1127
- const remoteExists = await remoteHeadBranchExists(cwd, branch);
1172
+ const remoteExists = await remoteBranchExists(gitRoot, branch);
1128
1173
  if (remoteExists) {
1129
- await execGit2(cwd, ["fetch", "origin", branch]);
1130
- await execGit2(cwd, ["checkout", "-B", branch, `origin/${branch}`]);
1131
- } else if (await localBranchExists(cwd, branch)) {
1132
- if (await getCurrentBranch2(cwd) !== branch) {
1133
- await execGit2(cwd, ["checkout", branch]);
1174
+ await execGit(gitRoot, ["fetch", "origin", branch]);
1175
+ await execGit(gitRoot, ["checkout", "-B", branch, `origin/${branch}`]);
1176
+ } else if (await localBranchExists(gitRoot, branch)) {
1177
+ if (await getCurrentBranch(gitRoot) !== branch) {
1178
+ await execGit(gitRoot, ["checkout", branch]);
1134
1179
  }
1135
1180
  console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
1136
1181
  } else {
1137
- await execGit2(cwd, ["fetch", "origin", baselineBranch]);
1182
+ await execGit(gitRoot, ["fetch", "origin", baselineBranch]);
1138
1183
  try {
1139
- await execGit2(cwd, [
1184
+ await execGit(gitRoot, [
1140
1185
  "checkout",
1141
1186
  "-b",
1142
1187
  branch,
1143
1188
  `origin/${baselineBranch}`
1144
1189
  ]);
1145
- await execGit2(cwd, ["push", "-u", "origin", branch]);
1190
+ await execGit(gitRoot, ["push", "-u", "origin", branch]);
1146
1191
  } catch (err) {
1147
- if (await localBranchExists(cwd, branch)) {
1148
- if (await getCurrentBranch2(cwd) !== branch) {
1149
- await execGit2(cwd, ["checkout", branch]);
1192
+ if (await localBranchExists(gitRoot, branch)) {
1193
+ if (await getCurrentBranch(gitRoot) !== branch) {
1194
+ await execGit(gitRoot, ["checkout", branch]);
1150
1195
  }
1151
1196
  console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
1152
1197
  } else {
@@ -1168,115 +1213,168 @@ async function runBranch(sessionId, options = {}) {
1168
1213
  const api = createApmApiClient(cfg);
1169
1214
  const cwd = options.cwd ?? process.cwd();
1170
1215
  const workdirPath = resolveWorkdirPath(cwd);
1171
- const baseline = await api.cli.branchBaseline({
1172
- sessionId: trimmedSessionId,
1216
+ const baseline = await resolveBranchBaseline(
1217
+ api,
1218
+ trimmedSessionId,
1173
1219
  workdirPath
1174
- });
1175
- if (!baseline.repositoryId) {
1176
- const detail = baseline.diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baseline.workdirPath}\uFF09
1177
- \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
1178
- throw new Error(`[apm] ${detail}`);
1179
- }
1180
- const baselineBranch = (baseline.defaultBranch ?? "").trim();
1181
- if (!baselineBranch) {
1182
- throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
1183
- }
1220
+ );
1184
1221
  const branch = branchNameForSession(trimmedSessionId);
1185
- return ensureFeatureBranch(branch, baselineBranch, options);
1222
+ return ensureFeatureBranch(branch, baseline.defaultBranch, options);
1186
1223
  }
1187
1224
 
1188
- // src/commands/clean-branches.ts
1189
- import { execFile as execFile4 } from "child_process";
1190
- import { promisify as promisify4 } from "util";
1191
- var execFileAsync4 = promisify4(execFile4);
1192
- async function execGit3(cwd, args, quiet) {
1193
- try {
1194
- const { stdout, stderr } = await execFileAsync4("git", args, {
1195
- cwd,
1196
- encoding: "utf8",
1197
- maxBuffer: 10 * 1024 * 1024
1198
- });
1199
- if (!quiet && stderr.trim()) {
1200
- process.stderr.write(stderr);
1201
- }
1202
- return stdout;
1203
- } catch (err) {
1204
- const e = err;
1205
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
1206
- throw new Error(
1207
- `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
1208
- );
1225
+ // src/commands/session-branch-prune.ts
1226
+ var TASK_STATUS_LABEL = {
1227
+ OPEN: "\u5F85\u5F00\u59CB",
1228
+ IN_PROGRESS: "\u8FDB\u884C\u4E2D",
1229
+ COMPLETED: "\u5DF2\u5B8C\u6210",
1230
+ FAILED: "\u5DF2\u5931\u8D25",
1231
+ CLOSED: "\u5DF2\u5173\u95ED",
1232
+ SUSPENDED: "\u5DF2\u6682\u505C"
1233
+ };
1234
+ function taskStatusLabel(status) {
1235
+ if (status === null) {
1236
+ return "\u5E73\u53F0\u65E0\u4EFB\u52A1";
1209
1237
  }
1238
+ return TASK_STATUS_LABEL[status] ?? status;
1210
1239
  }
1211
- async function ensureGitRepo3(cwd) {
1212
- await execGit3(cwd, ["rev-parse", "--git-dir"], true);
1240
+ function syncTagForBranch(hasLocal, hasRemote) {
1241
+ if (hasLocal && hasRemote) {
1242
+ return "\u53CC\u7AEF";
1243
+ }
1244
+ if (hasLocal) {
1245
+ return "\u4EC5\u672C\u5730";
1246
+ }
1247
+ return "\u4EC5\u8FDC\u7A0B";
1213
1248
  }
1214
- async function getCurrentBranch3(cwd) {
1215
- return (await execGit3(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1249
+ function shouldDeleteMode1(input) {
1250
+ if (input.merged) {
1251
+ return { delete: true, reason: "\u5DF2\u5408\u5E76" };
1252
+ }
1253
+ if (input.taskStatus === null) {
1254
+ return { delete: true, reason: "\u5E73\u53F0\u65E0\u4EFB\u52A1" };
1255
+ }
1256
+ return { delete: false, reason: "" };
1216
1257
  }
1217
- async function resolveBaselineBranch(cwd, api) {
1218
- const workdirPath = resolveWorkdirPath(cwd);
1219
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
1220
- if (!baseline.repositoryId) {
1221
- const detail = baseline.diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baseline.workdirPath}\uFF09
1222
- \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
1223
- throw new Error(`[apm] ${detail}`);
1258
+ function shouldDeleteMode2(input) {
1259
+ const mode1 = shouldDeleteMode1(input);
1260
+ if (mode1.delete) {
1261
+ return mode1;
1224
1262
  }
1225
- const baselineBranch = (baseline.defaultBranch ?? "").trim();
1226
- if (!baselineBranch) {
1227
- throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
1263
+ if (input.taskStatus === "COMPLETED") {
1264
+ return { delete: true, reason: "\u4EFB\u52A1\u5DF2\u5B8C\u6210" };
1228
1265
  }
1229
- await execGit3(cwd, ["fetch", "origin", baselineBranch], true);
1230
- if (!await remoteBranchExists2(cwd, baselineBranch)) {
1231
- throw new Error(
1232
- `[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F origin/${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
1233
- );
1266
+ if (input.taskStatus === "FAILED") {
1267
+ return { delete: true, reason: "\u4EFB\u52A1\u5DF2\u5931\u8D25" };
1234
1268
  }
1235
- return baselineBranch;
1236
- }
1237
- async function isBranchBasedOnBaseline(cwd, branch, baselineBranch) {
1238
- const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
1239
- try {
1240
- await execGit3(
1241
- cwd,
1242
- ["merge-base", "--is-ancestor", `origin/${baselineBranch}`, ref],
1243
- true
1244
- );
1245
- return true;
1246
- } catch {
1247
- return false;
1269
+ if (input.taskStatus === "CLOSED") {
1270
+ return { delete: true, reason: "\u4EFB\u52A1\u5DF2\u5173\u95ED" };
1248
1271
  }
1272
+ return { delete: false, reason: "" };
1249
1273
  }
1250
- async function listCandidateSessionBranches(cwd, includeRemote) {
1251
- const names = new Set(await listLocalSessionBranches(cwd));
1252
- if (includeRemote) {
1253
- for (const branch of await listRemoteSessionBranches(cwd)) {
1254
- names.add(branch);
1255
- }
1274
+ function buildSessionBranchRow(input) {
1275
+ const mode1 = shouldDeleteMode1(input);
1276
+ const mode2 = shouldDeleteMode2(input);
1277
+ return {
1278
+ branch: input.branch,
1279
+ sessionId: input.sessionId,
1280
+ hasLocal: input.hasLocal,
1281
+ hasRemote: input.hasRemote,
1282
+ syncTag: syncTagForBranch(input.hasLocal, input.hasRemote),
1283
+ merged: input.merged,
1284
+ taskStatus: input.taskStatus,
1285
+ taskLabel: taskStatusLabel(input.taskStatus),
1286
+ deleteMode1: mode1.delete,
1287
+ deleteMode2: mode2.delete,
1288
+ deleteReasonMode1: mode1.reason,
1289
+ deleteReasonMode2: mode2.reason
1290
+ };
1291
+ }
1292
+ function rowsForDeleteMode(rows, mode) {
1293
+ return rows.filter((row) => mode === 1 ? row.deleteMode1 : row.deleteMode2);
1294
+ }
1295
+ function yn(value) {
1296
+ return value ? "\u662F" : "\u2014";
1297
+ }
1298
+ function pad(value, width) {
1299
+ const chars = [...value];
1300
+ const len = chars.length;
1301
+ if (len >= width) {
1302
+ return value;
1256
1303
  }
1257
- return [...names].sort((a, b) => a.localeCompare(b));
1304
+ return value + " ".repeat(width - len);
1305
+ }
1306
+ function formatSessionBranchTable(meta, rows) {
1307
+ const lines = [
1308
+ `[apm] \u5DE5\u4F5C\u76EE\u5F55: ${meta.workdirPath}`,
1309
+ `[apm] Git \u6839\u76EE\u5F55: ${meta.gitRoot}`,
1310
+ `[apm] \u4ED3\u5E93 ID: ${meta.repositoryId}`,
1311
+ `[apm] \u57FA\u7EBF\u5206\u652F: ${meta.baselineBranch}`,
1312
+ ""
1313
+ ];
1314
+ if (rows.length === 0) {
1315
+ lines.push(
1316
+ `[apm] \u672A\u53D1\u73B0\u4ECE ${meta.baselineBranch} \u884D\u751F\u7684 feat/session-* \u5206\u652F\uFF08\u672C\u5730\u4E0E\u8FDC\u7A0B\uFF09`
1317
+ );
1318
+ return lines.join("\n");
1319
+ }
1320
+ const headers = [
1321
+ "\u5206\u652F",
1322
+ "\u672C\u5730",
1323
+ "\u8FDC\u7A0B",
1324
+ "\u540C\u6B65",
1325
+ "\u5DF2\u5408\u5E76",
1326
+ "\u4EFB\u52A1\u72B6\u6001",
1327
+ "\u6A21\u5F0F1",
1328
+ "\u6A21\u5F0F2"
1329
+ ];
1330
+ const widths = [36, 4, 4, 6, 6, 10, 6, 6];
1331
+ lines.push(headers.map((h, i) => pad(h, widths[i])).join(" "));
1332
+ lines.push(widths.map((w) => "-".repeat(w)).join(" "));
1333
+ for (const row of rows) {
1334
+ const cells = [
1335
+ row.branch,
1336
+ yn(row.hasLocal),
1337
+ yn(row.hasRemote),
1338
+ row.syncTag,
1339
+ yn(row.merged),
1340
+ row.taskLabel,
1341
+ row.deleteMode1 ? row.deleteReasonMode1 || "\u662F" : "\u2014",
1342
+ row.deleteMode2 ? row.deleteReasonMode2 || "\u662F" : "\u2014"
1343
+ ];
1344
+ lines.push(cells.map((c, i) => pad(c, widths[i])).join(" "));
1345
+ }
1346
+ const mode1Count = rows.filter((r) => r.deleteMode1).length;
1347
+ const mode2Count = rows.filter((r) => r.deleteMode2).length;
1348
+ lines.push("");
1349
+ lines.push(
1350
+ `[apm] \u5171 ${rows.length} \u4E2A\u5206\u652F\uFF1B\u6A21\u5F0F 1 \u53EF\u6E05\u7406 ${mode1Count} \u4E2A\uFF0C\u6A21\u5F0F 2 \u53EF\u6E05\u7406 ${mode2Count} \u4E2A`
1351
+ );
1352
+ lines.push("[apm] \u6A21\u5F0F 1\uFF1A\u5DF2\u5408\u5E76\u8FDB\u57FA\u7EBF\uFF0C\u6216\u5E73\u53F0\u5DF2\u65E0\u8BE5\u6C9F\u901A\u7FA4\u4EFB\u52A1");
1353
+ lines.push("[apm] \u6A21\u5F0F 2\uFF1A\u5728\u6A21\u5F0F 1 \u57FA\u7840\u4E0A\uFF0C\u53E6\u542B\u4EFB\u52A1\u5DF2\u5B8C\u6210\u6216\u5DF2\u5931\u8D25\u7684\u5206\u652F");
1354
+ lines.push("[apm] \u6267\u884C\u6E05\u7406: apm branch prune --mode 1|2 [--dry-run]");
1355
+ return lines.join("\n");
1258
1356
  }
1259
1357
  async function listLocalSessionBranches(cwd) {
1260
- const out = await execGit3(
1358
+ const out = await execGit(
1261
1359
  cwd,
1262
1360
  [
1263
1361
  "for-each-ref",
1264
1362
  "--format=%(refname:short)",
1265
1363
  "refs/heads/",
1266
- SESSION_BRANCH_PREFIX + "*"
1364
+ `${SESSION_BRANCH_PREFIX}*`
1267
1365
  ],
1268
1366
  true
1269
1367
  );
1270
1368
  return out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1271
1369
  }
1272
1370
  async function listRemoteSessionBranches(cwd) {
1273
- const out = await execGit3(
1371
+ const out = await execGit(
1274
1372
  cwd,
1275
1373
  [
1276
1374
  "for-each-ref",
1277
1375
  "--format=%(refname:short)",
1278
1376
  "refs/remotes/origin/",
1279
- SESSION_BRANCH_PREFIX + "*"
1377
+ `${SESSION_BRANCH_PREFIX}*`
1280
1378
  ],
1281
1379
  true
1282
1380
  );
@@ -1284,7 +1382,7 @@ async function listRemoteSessionBranches(cwd) {
1284
1382
  }
1285
1383
  async function localBranchExists2(cwd, branch) {
1286
1384
  try {
1287
- await execGit3(
1385
+ await execGit(
1288
1386
  cwd,
1289
1387
  ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
1290
1388
  true
@@ -1294,29 +1392,25 @@ async function localBranchExists2(cwd, branch) {
1294
1392
  return false;
1295
1393
  }
1296
1394
  }
1297
- async function remoteBranchExists2(cwd, branch) {
1298
- const out = await execGit3(
1299
- cwd,
1300
- ["ls-remote", "--heads", "origin", branch],
1301
- true
1302
- );
1303
- return out.trim().length > 0;
1304
- }
1305
- function reasonForCleanup(sessionId, sessionStatusById) {
1306
- if (!sessionStatusById.has(sessionId)) {
1307
- return "\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D";
1308
- }
1309
- if (sessionStatusById.get(sessionId) === "COMPLETED") {
1310
- return "\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210";
1395
+ async function isBranchBasedOnBaseline(cwd, branch, baselineBranch) {
1396
+ const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
1397
+ try {
1398
+ await execGit(
1399
+ cwd,
1400
+ ["merge-base", "--is-ancestor", `origin/${baselineBranch}`, ref],
1401
+ true
1402
+ );
1403
+ return true;
1404
+ } catch {
1405
+ return false;
1311
1406
  }
1312
- return "\u4FDD\u7559";
1313
1407
  }
1314
- async function isBranchMergedIntoDefault(cwd, branch, defaultBranch) {
1408
+ async function isBranchMergedIntoBaseline(cwd, branch, baselineBranch) {
1315
1409
  const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
1316
1410
  try {
1317
- await execGit3(
1411
+ await execGit(
1318
1412
  cwd,
1319
- ["merge-base", "--is-ancestor", ref, `origin/${defaultBranch}`],
1413
+ ["merge-base", "--is-ancestor", ref, `origin/${baselineBranch}`],
1320
1414
  true
1321
1415
  );
1322
1416
  return true;
@@ -1324,79 +1418,129 @@ async function isBranchMergedIntoDefault(cwd, branch, defaultBranch) {
1324
1418
  return false;
1325
1419
  }
1326
1420
  }
1421
+ async function resolveSessionBranchScanMeta(cwd, api) {
1422
+ const workdirPath = resolveWorkdirPath(cwd);
1423
+ const baseline = await resolveWorkspaceBaseline(api, workdirPath);
1424
+ const gitRoot = await resolveGitRepoRoot(cwd);
1425
+ await ensureGitRepo(gitRoot);
1426
+ await execGit(gitRoot, ["fetch", "--prune", "origin"], true);
1427
+ await ensureRemoteBaselineBranch(gitRoot, baseline.defaultBranch);
1428
+ return {
1429
+ workdirPath,
1430
+ gitRoot,
1431
+ baselineBranch: baseline.defaultBranch,
1432
+ repositoryId: baseline.repositoryId
1433
+ };
1434
+ }
1435
+ async function scanSessionBranches(meta, sessionStatusById) {
1436
+ const { gitRoot, baselineBranch } = meta;
1437
+ const names = /* @__PURE__ */ new Set();
1438
+ for (const branch of await listLocalSessionBranches(gitRoot)) {
1439
+ names.add(branch);
1440
+ }
1441
+ for (const branch of await listRemoteSessionBranches(gitRoot)) {
1442
+ names.add(branch);
1443
+ }
1444
+ const rows = [];
1445
+ for (const branch of [...names].sort((a, b) => a.localeCompare(b))) {
1446
+ if (!await isBranchBasedOnBaseline(gitRoot, branch, baselineBranch)) {
1447
+ continue;
1448
+ }
1449
+ const sessionId = sessionIdFromBranchName(branch);
1450
+ if (!sessionId) {
1451
+ continue;
1452
+ }
1453
+ const hasLocal = await localBranchExists2(gitRoot, branch);
1454
+ const hasRemote = await remoteBranchExists(gitRoot, branch);
1455
+ const merged = await isBranchMergedIntoBaseline(
1456
+ gitRoot,
1457
+ branch,
1458
+ baselineBranch
1459
+ );
1460
+ const rawStatus = sessionStatusById.get(sessionId);
1461
+ const taskStatus = rawStatus ? rawStatus : null;
1462
+ rows.push(
1463
+ buildSessionBranchRow({
1464
+ branch,
1465
+ sessionId,
1466
+ hasLocal,
1467
+ hasRemote,
1468
+ merged,
1469
+ taskStatus
1470
+ })
1471
+ );
1472
+ }
1473
+ return rows;
1474
+ }
1475
+ async function deleteSessionBranch(gitRoot, baselineBranch, branch, currentBranchRef) {
1476
+ if (currentBranchRef.value === branch) {
1477
+ await execGit(gitRoot, ["checkout", baselineBranch], true);
1478
+ currentBranchRef.value = baselineBranch;
1479
+ }
1480
+ if (await localBranchExists2(gitRoot, branch)) {
1481
+ await execGit(gitRoot, ["branch", "-D", branch], true);
1482
+ console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
1483
+ }
1484
+ if (await remoteBranchExists(gitRoot, branch)) {
1485
+ await execGit(gitRoot, ["push", "origin", "--delete", branch], true);
1486
+ console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
1487
+ }
1488
+ }
1489
+ async function loadSessionStatusMap(api) {
1490
+ const { sessions } = await api.cli.listSessionsForBranchCleanup({});
1491
+ return new Map(sessions.map((item) => [item.sessionId, item.taskStatus]));
1492
+ }
1493
+ async function getGitCurrentBranch(gitRoot) {
1494
+ return getCurrentBranch(gitRoot);
1495
+ }
1496
+
1497
+ // src/commands/clean-branches.ts
1327
1498
  async function runCleanBranches(options = {}) {
1328
1499
  const cwd = options.cwd ?? process.cwd();
1329
- const dryRun = options.dryRun ?? false;
1330
- const includeRemote = options.includeRemote ?? false;
1331
- await ensureGitRepo3(cwd);
1332
- await execGit3(cwd, ["fetch", "--prune", "origin"], true);
1333
1500
  const cfg = await ensureLoggedConfig();
1334
1501
  const api = createApmApiClient(cfg);
1335
- const baselineBranch = await resolveBaselineBranch(cwd, api);
1336
- const { sessions } = await api.cli.listSessionsForBranchCleanup({});
1337
- const sessionStatusById = new Map(
1338
- sessions.map((item) => [item.sessionId, item.taskStatus])
1339
- );
1340
- const candidates = await listCandidateSessionBranches(cwd, includeRemote);
1341
- const branchNames = [];
1342
- for (const branch of candidates) {
1343
- if (await isBranchBasedOnBaseline(cwd, branch, baselineBranch)) {
1344
- branchNames.push(branch);
1345
- }
1346
- }
1347
- if (branchNames.length === 0) {
1348
- console.log(
1349
- `[apm] \u672A\u53D1\u73B0\u4ECE ${baselineBranch} \u884D\u751F\u7684\u672C\u5730 feat/session-* \u5206\u652F${includeRemote ? "\uFF08\u542B\u8FDC\u7A0B\uFF09" : ""}`
1350
- );
1502
+ const meta = await resolveSessionBranchScanMeta(cwd, api);
1503
+ const sessionStatusById = await loadSessionStatusMap(api);
1504
+ const rows = await scanSessionBranches(meta, sessionStatusById);
1505
+ console.log(formatSessionBranchTable(meta, rows));
1506
+ if (!options.mode) {
1351
1507
  return;
1352
1508
  }
1353
- const toDelete = [...branchNames].map((branch) => {
1354
- const sessionId = sessionIdFromBranchName(branch);
1355
- if (!sessionId) {
1356
- return null;
1357
- }
1358
- const reason = reasonForCleanup(sessionId, sessionStatusById);
1359
- if (reason === "\u4FDD\u7559") {
1360
- return null;
1361
- }
1362
- return { branch, sessionId, reason };
1363
- }).filter((item) => item != null).sort((a, b) => a.branch.localeCompare(b.branch));
1509
+ const toDelete = rowsForDeleteMode(rows, options.mode);
1364
1510
  if (toDelete.length === 0) {
1365
- console.log("[apm] \u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684 feat/session-* \u5206\u652F");
1511
+ console.log(`[apm] \u6A21\u5F0F ${options.mode} \u4E0B\u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684\u5206\u652F`);
1366
1512
  return;
1367
1513
  }
1368
- let currentBranch = await getCurrentBranch3(cwd);
1369
- for (const item of toDelete) {
1370
- const { branch, sessionId, reason } = item;
1371
- const label = `${branch} (${sessionId}: ${reason})`;
1372
- if (dryRun) {
1373
- const merged = await isBranchMergedIntoDefault(
1374
- cwd,
1375
- branch,
1376
- baselineBranch
1514
+ if (options.dryRun) {
1515
+ for (const row of toDelete) {
1516
+ const reason = options.mode === 1 ? row.deleteReasonMode1 : row.deleteReasonMode2;
1517
+ const parts = [
1518
+ row.hasLocal ? "\u672C\u5730" : null,
1519
+ row.hasRemote ? "\u8FDC\u7A0B" : null
1520
+ ].filter(Boolean).join("+");
1521
+ console.log(
1522
+ `[apm] [dry-run] \u6A21\u5F0F ${options.mode} \u5C06\u5220\u9664 ${row.branch}\uFF08${parts || "\u65E0"}\uFF1B${reason}\uFF09`
1377
1523
  );
1378
- const mergeTag = merged ? "\u5DF2\u5408\u5E76" : "\u672A\u5408\u5E76";
1379
- console.log(`[apm] [dry-run] \u5C06\u5220\u9664 ${label} [${mergeTag}]`);
1380
- continue;
1381
- }
1382
- if (currentBranch === branch) {
1383
- await execGit3(cwd, ["checkout", baselineBranch], true);
1384
- currentBranch = baselineBranch;
1385
- }
1386
- if (await localBranchExists2(cwd, branch)) {
1387
- await execGit3(cwd, ["branch", "-D", branch], true);
1388
- console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
1389
- }
1390
- if (await remoteBranchExists2(cwd, branch)) {
1391
- await execGit3(cwd, ["push", "origin", "--delete", branch], true);
1392
- console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
1393
1524
  }
1525
+ console.log(
1526
+ `[apm] [dry-run] \u6A21\u5F0F ${options.mode} \u5171 ${toDelete.length} \u4E2A\u5206\u652F\u5F85\u6E05\u7406`
1527
+ );
1528
+ return;
1394
1529
  }
1395
- if (dryRun) {
1396
- console.log(`[apm] [dry-run] \u5171 ${toDelete.length} \u4E2A\u5206\u652F\u5F85\u6E05\u7406`);
1397
- } else {
1398
- console.log(`[apm] \u5DF2\u6E05\u7406 ${toDelete.length} \u4E2A feat/session-* \u5206\u652F`);
1530
+ const currentBranchRef = {
1531
+ value: await getGitCurrentBranch(meta.gitRoot)
1532
+ };
1533
+ for (const row of toDelete) {
1534
+ await deleteSessionBranch(
1535
+ meta.gitRoot,
1536
+ meta.baselineBranch,
1537
+ row.branch,
1538
+ currentBranchRef
1539
+ );
1399
1540
  }
1541
+ console.log(
1542
+ `[apm] \u6A21\u5F0F ${options.mode} \u5DF2\u6E05\u7406 ${toDelete.length} \u4E2A feat/session-* \u5206\u652F`
1543
+ );
1400
1544
  }
1401
1545
 
1402
1546
  // src/commands/pull.ts
@@ -1649,7 +1793,7 @@ function isRuleUpToDate(entry, rule, dest) {
1649
1793
  }
1650
1794
  async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
1651
1795
  const api = createApmApiClient(cfg);
1652
- const baseline = await api.cli.branchBaseline({ sessionId, workdirPath });
1796
+ const baseline = await resolveBranchBaseline(api, sessionId, workdirPath);
1653
1797
  const repositoryId = baseline.repositoryId;
1654
1798
  const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
1655
1799
  await ensureDirExists(rulesDir);
@@ -2881,15 +3025,25 @@ function posixBasename(p) {
2881
3025
  }
2882
3026
 
2883
3027
  // src/commands/deploy/internal/deploy-hints.ts
3028
+ function isProductionDeployEnv(env) {
3029
+ const normalized = env.trim().toLowerCase();
3030
+ return normalized === "online" || normalized === "production" || normalized === "prod";
3031
+ }
2884
3032
  function formatMissingWisdomDeployLines() {
2885
3033
  return [
2886
3034
  "[apm] \u65E0\u6CD5\u6267\u884C apm deploy\uFF1A\u8BF7\u5728 apm.config.json \u4E2D\u914D\u7F6E wisdomDeploy\uFF08host\u3001remotePath\uFF09"
2887
3035
  ];
2888
3036
  }
2889
3037
  function formatWisdomFrontendBuildNotConfiguredLines(env, packageJsonPath) {
3038
+ if (isProductionDeployEnv(env)) {
3039
+ return [
3040
+ `[apm] \u65E0\u6CD5\u6267\u884C\u524D\u7AEF\u90E8\u7F72\uFF1A${packageJsonPath} \u4E2D\u672A\u627E\u5230 build:${env} \u6216 build \u811A\u672C`,
3041
+ "[apm] \u8BF7\u914D\u7F6E npm run build:online \u6216 npm run build \u540E\u518D\u8BD5"
3042
+ ];
3043
+ }
2890
3044
  return [
2891
3045
  `[apm] \u65E0\u6CD5\u6267\u884C\u524D\u7AEF\u90E8\u7F72\uFF1A${packageJsonPath} \u4E2D\u672A\u627E\u5230 build:${env} \u811A\u672C`,
2892
- "[apm] \u8BF7\u914D\u7F6E npm run build:test / build:online \u540E\u518D\u8BD5"
3046
+ "[apm] \u8BF7\u914D\u7F6E npm run build:test \u540E\u518D\u8BD5"
2893
3047
  ];
2894
3048
  }
2895
3049
 
@@ -3054,8 +3208,8 @@ var MinioClient = class {
3054
3208
  // src/commands/deploy/internal/deploy-artifact-minio.ts
3055
3209
  var APM_DEPLOYMENT_RUN_ID_ENV = "APM_DEPLOYMENT_RUN_ID";
3056
3210
  function formatDeployArtifactTimestamp(date = /* @__PURE__ */ new Date()) {
3057
- const pad = (n) => String(n).padStart(2, "0");
3058
- return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
3211
+ const pad2 = (n) => String(n).padStart(2, "0");
3212
+ return `${date.getFullYear()}${pad2(date.getMonth() + 1)}${pad2(date.getDate())}${pad2(date.getHours())}${pad2(date.getMinutes())}${pad2(date.getSeconds())}`;
3059
3213
  }
3060
3214
  function sanitizeDeployProjectName(name) {
3061
3215
  const trimmed = name.trim().replace(/\\/g, "/");
@@ -5144,6 +5298,9 @@ function resolveFrontendBuildCommand(env, cwd) {
5144
5298
  if (scripts[buildKey]?.trim()) {
5145
5299
  return `npm run build:${env}`;
5146
5300
  }
5301
+ if (isProductionDeployEnv(env) && scripts.build?.trim()) {
5302
+ return "npm run build";
5303
+ }
5147
5304
  return null;
5148
5305
  }
5149
5306
  function resolveFrontendDistDir(cwd) {
@@ -5254,19 +5411,10 @@ var LOG_PREFIX2 = "[apm] \u90E8\u7F72\u524D\u57FA\u7EBF\u540C\u6B65:";
5254
5411
  function formatGitError(error) {
5255
5412
  return error instanceof Error ? error.message : String(error);
5256
5413
  }
5257
- async function resolveBaselineBranch2(cwd, api) {
5414
+ async function resolveBaselineBranch(cwd, api) {
5258
5415
  const workdirPath = resolveWorkdirPath(cwd);
5259
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
5260
- if (!baseline.repositoryId) {
5261
- const detail = baseline.diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baseline.workdirPath}\uFF09
5262
- \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
5263
- throw new Error(detail);
5264
- }
5265
- const baselineBranch = (baseline.defaultBranch ?? "").trim();
5266
- if (!baselineBranch) {
5267
- throw new Error("\u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
5268
- }
5269
- return baselineBranch;
5416
+ const baseline = await resolveWorkspaceBaseline(api, workdirPath);
5417
+ return baseline.defaultBranch;
5270
5418
  }
5271
5419
  async function isMergeInProgress(cwd) {
5272
5420
  try {
@@ -5346,21 +5494,17 @@ async function syncDeployBaselineBeforeDeploy(cwd) {
5346
5494
  }
5347
5495
  await ensureGitRepo(cwd);
5348
5496
  const api = createApmApiClient(cfg);
5349
- const baselineBranch = await resolveBaselineBranch2(cwd, api);
5497
+ const baselineBranch = await resolveBaselineBranch(cwd, api);
5498
+ const gitRoot = await resolveGitRepoRoot(cwd);
5350
5499
  note(`${LOG_PREFIX2} \u57FA\u7EBF\u5206\u652F ${baselineBranch}`);
5351
5500
  note(`${LOG_PREFIX2} \u6B63\u5728 fetch origin ${baselineBranch}`);
5352
- await execGit(cwd, ["fetch", "origin", baselineBranch], true);
5353
- if (!await remoteBranchExists(cwd, baselineBranch)) {
5354
- throw new Error(
5355
- `\u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F origin/${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
5356
- );
5357
- }
5501
+ await ensureRemoteBaselineBranch(gitRoot, baselineBranch);
5358
5502
  note(`${LOG_PREFIX2} fetch \u5B8C\u6210`);
5359
- const currentBranch = await getCurrentBranch(cwd);
5503
+ const currentBranch = await getCurrentBranch(gitRoot);
5360
5504
  let stashed = false;
5361
- if (await isWorkingTreeDirty(cwd)) {
5505
+ if (await isWorkingTreeDirty(gitRoot)) {
5362
5506
  note(`${LOG_PREFIX2} \u68C0\u6D4B\u5230\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u5148 stash`);
5363
- await execGit(cwd, [
5507
+ await execGit(gitRoot, [
5364
5508
  "stash",
5365
5509
  "push",
5366
5510
  "-u",
@@ -5371,12 +5515,12 @@ async function syncDeployBaselineBeforeDeploy(cwd) {
5371
5515
  note(`${LOG_PREFIX2} \u5DF2 stash \u672A\u63D0\u4EA4\u6539\u52A8`);
5372
5516
  }
5373
5517
  const { merged, conflictRollback } = await mergeBaselineBranch(
5374
- cwd,
5518
+ gitRoot,
5375
5519
  baselineBranch,
5376
5520
  currentBranch,
5377
5521
  note
5378
5522
  );
5379
- const stashConflict = await restoreStashIfNeeded(cwd, stashed, note);
5523
+ const stashConflict = await restoreStashIfNeeded(gitRoot, stashed, note);
5380
5524
  if (conflictRollback || stashConflict) {
5381
5525
  note(`${LOG_PREFIX2} \u57FA\u7EBF\u540C\u6B65\u672A\u5B8C\u6210\uFF08\u5B58\u5728\u51B2\u7A81\u5E76\u5DF2\u56DE\u9000\uFF09\uFF0C\u7EE7\u7EED\u6267\u884C\u90E8\u7F72`);
5382
5526
  } else if (merged) {
@@ -8258,14 +8402,23 @@ function buildProgram() {
8258
8402
  });
8259
8403
  const branch = program.command("branch").description("\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>");
8260
8404
  branch.command("prune").description(
8261
- "\u6E05\u7406\u672C\u5730\u4E0E\u8FDC\u7A0B feat/session-* \u5206\u652F\uFF08\u7C7B\u4F3C git fetch --prune\uFF09\uFF1A\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D\uFF0C\u6216\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210\u65F6\u5220\u9664"
8262
- ).option("--dry-run", "\u4EC5\u5217\u51FA\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").option(
8263
- "-A, --all",
8264
- "\u5305\u542B\u4EC5\u5B58\u5728\u4E8E\u8FDC\u7A0B\u3001\u672C\u5730\u672A checkout \u7684 feat/session-* \u5206\u652F\uFF08\u9ED8\u8BA4\u4EC5\u5904\u7406\u672C\u5730\u5206\u652F\uFF09"
8265
- ).action(async (opts) => {
8405
+ "\u626B\u63CF feat/session-* \u5206\u652F\u5E76\u8F93\u51FA\u72B6\u6001\u8868\uFF1B\u52A0 --mode \u6309\u89C4\u5219\u6E05\u7406\u672C\u5730\u4E0E\u8FDC\u7A0B\u5206\u652F"
8406
+ ).option(
8407
+ "--mode <n>",
8408
+ "\u6E05\u7406\u6A21\u5F0F\uFF1A1=\u5DF2\u5408\u5E76\u6216\u5E73\u53F0\u65E0\u4EFB\u52A1\uFF1B2=\u6A21\u5F0F1+\u4EFB\u52A1\u5DF2\u5B8C\u6210/\u5DF2\u5931\u8D25"
8409
+ ).option("--dry-run", "\u914D\u5408 --mode \u4EC5\u9884\u89C8\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (opts) => {
8410
+ const modeRaw = opts.mode?.trim();
8411
+ let mode;
8412
+ if (modeRaw) {
8413
+ if (modeRaw !== "1" && modeRaw !== "2") {
8414
+ console.error("[apm] --mode \u4EC5\u652F\u6301 1 \u6216 2");
8415
+ process.exit(1);
8416
+ }
8417
+ mode = Number(modeRaw);
8418
+ }
8266
8419
  await runCleanBranches({
8267
8420
  dryRun: opts.dryRun,
8268
- includeRemote: opts.all
8421
+ mode
8269
8422
  });
8270
8423
  });
8271
8424
  branch.description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.105",
3
+ "version": "6.0.107",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,