ai-project-manage-cli 6.0.51 → 6.0.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +184 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -407,6 +407,10 @@ var requestConfig = {
|
|
|
407
407
|
method: "GET",
|
|
408
408
|
path: "/cli/tasks/branch-baseline"
|
|
409
409
|
}),
|
|
410
|
+
listSessionsForBranchCleanup: defineEndpoint({
|
|
411
|
+
method: "GET",
|
|
412
|
+
path: "/cli/sessions/branch-cleanup"
|
|
413
|
+
}),
|
|
410
414
|
workspaceBaseline: defineEndpoint({
|
|
411
415
|
method: "GET",
|
|
412
416
|
path: "/cli/workspaces/baseline"
|
|
@@ -634,6 +638,7 @@ async function runLogin(opts) {
|
|
|
634
638
|
import { execFile as execFile2 } from "child_process";
|
|
635
639
|
import { promisify as promisify2 } from "util";
|
|
636
640
|
var execFileAsync2 = promisify2(execFile2);
|
|
641
|
+
var SESSION_BRANCH_PREFIX = "feat/session-";
|
|
637
642
|
function branchNameForSession(sessionId) {
|
|
638
643
|
const id = sessionId.trim();
|
|
639
644
|
if (!id) {
|
|
@@ -644,7 +649,15 @@ function branchNameForSession(sessionId) {
|
|
|
644
649
|
"[apm] \u4F1A\u8BDD ID \u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u6216\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BCD\u3001\u6570\u5B57\u3001._- \u7B49"
|
|
645
650
|
);
|
|
646
651
|
}
|
|
647
|
-
return
|
|
652
|
+
return `${SESSION_BRANCH_PREFIX}${id}`;
|
|
653
|
+
}
|
|
654
|
+
function sessionIdFromBranchName(branch) {
|
|
655
|
+
const name = branch.trim().replace(/^origin\//, "");
|
|
656
|
+
if (!name.startsWith(SESSION_BRANCH_PREFIX)) {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
const sessionId = name.slice(SESSION_BRANCH_PREFIX.length).trim();
|
|
660
|
+
return sessionId || null;
|
|
648
661
|
}
|
|
649
662
|
async function execGit(cwd, args, quiet) {
|
|
650
663
|
try {
|
|
@@ -797,6 +810,171 @@ async function runBranch(sessionId, options = {}) {
|
|
|
797
810
|
return ensureFeatureBranch(branch, baselineBranch, options);
|
|
798
811
|
}
|
|
799
812
|
|
|
813
|
+
// src/commands/clean-branches.ts
|
|
814
|
+
import { execFile as execFile3 } from "child_process";
|
|
815
|
+
import { promisify as promisify3 } from "util";
|
|
816
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
817
|
+
async function execGit2(cwd, args, quiet) {
|
|
818
|
+
try {
|
|
819
|
+
const { stdout, stderr } = await execFileAsync3("git", args, {
|
|
820
|
+
cwd,
|
|
821
|
+
encoding: "utf8",
|
|
822
|
+
maxBuffer: 10 * 1024 * 1024
|
|
823
|
+
});
|
|
824
|
+
if (!quiet && stderr.trim()) {
|
|
825
|
+
process.stderr.write(stderr);
|
|
826
|
+
}
|
|
827
|
+
return stdout;
|
|
828
|
+
} catch (err) {
|
|
829
|
+
const e = err;
|
|
830
|
+
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
831
|
+
throw new Error(
|
|
832
|
+
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function ensureGitRepo2(cwd) {
|
|
837
|
+
await execGit2(cwd, ["rev-parse", "--git-dir"], true);
|
|
838
|
+
}
|
|
839
|
+
async function getCurrentBranch2(cwd) {
|
|
840
|
+
return (await execGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
841
|
+
}
|
|
842
|
+
async function resolveDefaultBranch(cwd) {
|
|
843
|
+
try {
|
|
844
|
+
const ref = (await execGit2(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], true)).trim();
|
|
845
|
+
const match = ref.match(/^refs\/remotes\/origin\/(.+)$/);
|
|
846
|
+
if (match?.[1]) {
|
|
847
|
+
return match[1];
|
|
848
|
+
}
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
const out = await execGit2(cwd, ["remote", "show", "origin"], true);
|
|
852
|
+
const headLine = out.split(/\r?\n/).find((line) => line.includes("HEAD branch"));
|
|
853
|
+
const branch = headLine?.split(":").pop()?.trim();
|
|
854
|
+
if (branch) {
|
|
855
|
+
return branch;
|
|
856
|
+
}
|
|
857
|
+
throw new Error("[apm] \u65E0\u6CD5\u89E3\u6790 origin \u9ED8\u8BA4\u5206\u652F\uFF0C\u8BF7\u5148\u6267\u884C git fetch origin");
|
|
858
|
+
}
|
|
859
|
+
async function listLocalSessionBranches(cwd) {
|
|
860
|
+
const out = await execGit2(
|
|
861
|
+
cwd,
|
|
862
|
+
[
|
|
863
|
+
"for-each-ref",
|
|
864
|
+
"--format=%(refname:short)",
|
|
865
|
+
"refs/heads/",
|
|
866
|
+
SESSION_BRANCH_PREFIX + "*"
|
|
867
|
+
],
|
|
868
|
+
true
|
|
869
|
+
);
|
|
870
|
+
return out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
871
|
+
}
|
|
872
|
+
async function listRemoteSessionBranches(cwd) {
|
|
873
|
+
const out = await execGit2(
|
|
874
|
+
cwd,
|
|
875
|
+
[
|
|
876
|
+
"for-each-ref",
|
|
877
|
+
"--format=%(refname:short)",
|
|
878
|
+
"refs/remotes/origin/",
|
|
879
|
+
SESSION_BRANCH_PREFIX + "*"
|
|
880
|
+
],
|
|
881
|
+
true
|
|
882
|
+
);
|
|
883
|
+
return out.split(/\r?\n/).map((line) => line.trim().replace(/^origin\//, "")).filter(Boolean);
|
|
884
|
+
}
|
|
885
|
+
async function localBranchExists2(cwd, branch) {
|
|
886
|
+
try {
|
|
887
|
+
await execGit2(
|
|
888
|
+
cwd,
|
|
889
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
890
|
+
true
|
|
891
|
+
);
|
|
892
|
+
return true;
|
|
893
|
+
} catch {
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
async function remoteBranchExists(cwd, branch) {
|
|
898
|
+
const out = await execGit2(
|
|
899
|
+
cwd,
|
|
900
|
+
["ls-remote", "--heads", "origin", branch],
|
|
901
|
+
true
|
|
902
|
+
);
|
|
903
|
+
return out.trim().length > 0;
|
|
904
|
+
}
|
|
905
|
+
function reasonForCleanup(sessionId, sessionStatusById) {
|
|
906
|
+
if (!sessionStatusById.has(sessionId)) {
|
|
907
|
+
return "\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D";
|
|
908
|
+
}
|
|
909
|
+
if (sessionStatusById.get(sessionId) === "COMPLETED") {
|
|
910
|
+
return "\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210";
|
|
911
|
+
}
|
|
912
|
+
return "\u4FDD\u7559";
|
|
913
|
+
}
|
|
914
|
+
async function runCleanBranches(options = {}) {
|
|
915
|
+
const cwd = options.cwd ?? process.cwd();
|
|
916
|
+
const dryRun = options.dryRun ?? false;
|
|
917
|
+
await ensureGitRepo2(cwd);
|
|
918
|
+
await execGit2(cwd, ["fetch", "--prune", "origin"], true);
|
|
919
|
+
const cfg = await ensureLoggedConfig();
|
|
920
|
+
const api = createApmApiClient(cfg);
|
|
921
|
+
const { sessions } = await api.cli.listSessionsForBranchCleanup({});
|
|
922
|
+
const sessionStatusById = new Map(
|
|
923
|
+
sessions.map((item) => [item.sessionId, item.taskStatus])
|
|
924
|
+
);
|
|
925
|
+
const branchNames = /* @__PURE__ */ new Set([
|
|
926
|
+
...await listLocalSessionBranches(cwd),
|
|
927
|
+
...await listRemoteSessionBranches(cwd)
|
|
928
|
+
]);
|
|
929
|
+
if (branchNames.size === 0) {
|
|
930
|
+
console.log("[apm] \u672A\u53D1\u73B0 feat/session-* \u5206\u652F");
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
const toDelete = [...branchNames].map((branch) => {
|
|
934
|
+
const sessionId = sessionIdFromBranchName(branch);
|
|
935
|
+
if (!sessionId) {
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
const reason = reasonForCleanup(sessionId, sessionStatusById);
|
|
939
|
+
if (reason === "\u4FDD\u7559") {
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
942
|
+
return { branch, sessionId, reason };
|
|
943
|
+
}).filter((item) => item != null).sort((a, b) => a.branch.localeCompare(b.branch));
|
|
944
|
+
if (toDelete.length === 0) {
|
|
945
|
+
console.log("[apm] \u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684 feat/session-* \u5206\u652F");
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
let currentBranch = await getCurrentBranch2(cwd);
|
|
949
|
+
let defaultBranch = null;
|
|
950
|
+
for (const item of toDelete) {
|
|
951
|
+
const { branch, sessionId, reason } = item;
|
|
952
|
+
const label = `${branch} (${sessionId}: ${reason})`;
|
|
953
|
+
if (dryRun) {
|
|
954
|
+
console.log(`[apm] [dry-run] \u5C06\u5220\u9664 ${label}`);
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
if (currentBranch === branch) {
|
|
958
|
+
defaultBranch ??= await resolveDefaultBranch(cwd);
|
|
959
|
+
await execGit2(cwd, ["checkout", defaultBranch], true);
|
|
960
|
+
currentBranch = defaultBranch;
|
|
961
|
+
}
|
|
962
|
+
if (await localBranchExists2(cwd, branch)) {
|
|
963
|
+
await execGit2(cwd, ["branch", "-D", branch], true);
|
|
964
|
+
console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
|
|
965
|
+
}
|
|
966
|
+
if (await remoteBranchExists(cwd, branch)) {
|
|
967
|
+
await execGit2(cwd, ["push", "origin", "--delete", branch], true);
|
|
968
|
+
console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (dryRun) {
|
|
972
|
+
console.log(`[apm] [dry-run] \u5171 ${toDelete.length} \u4E2A\u5206\u652F\u5F85\u6E05\u7406`);
|
|
973
|
+
} else {
|
|
974
|
+
console.log(`[apm] \u5DF2\u6E05\u7406 ${toDelete.length} \u4E2A feat/session-* \u5206\u652F`);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
800
978
|
// src/commands/pull.ts
|
|
801
979
|
import { writeFileSync as writeFileSync7 } from "fs";
|
|
802
980
|
import { join as join8 } from "path";
|
|
@@ -3683,6 +3861,11 @@ function buildProgram() {
|
|
|
3683
3861
|
).action(async (sessionId, opts) => {
|
|
3684
3862
|
await runBranch(sessionId, { message: opts.message });
|
|
3685
3863
|
});
|
|
3864
|
+
program.command("clean-branches").description(
|
|
3865
|
+
"\u6E05\u7406\u672C\u5730\u4E0E\u8FDC\u7A0B feat/session-* \u5206\u652F\uFF1A\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D\uFF0C\u6216\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210\u65F6\u5220\u9664"
|
|
3866
|
+
).option("--dry-run", "\u4EC5\u5217\u51FA\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (opts) => {
|
|
3867
|
+
await runCleanBranches({ dryRun: opts.dryRun });
|
|
3868
|
+
});
|
|
3686
3869
|
program.command("create-pr").description(
|
|
3687
3870
|
"\u4E3A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u7684\u4F1A\u8BDD\u7279\u6027\u5206\u652F\u521B\u5EFA PR\uFF08\u6807\u9898\u81EA\u52A8\u52A0 [AI] \u6807\u8BC6\uFF1B\u8FDC\u7A0B\u521B\u5EFA\u5931\u8D25\u65F6\u5E73\u53F0\u6570\u636E\u56DE\u6EDA\uFF09"
|
|
3688
3871
|
).requiredOption("--session <sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--title <title>", "PR \u6807\u9898").option("--content <content>", "PR \u6B63\u6587\uFF08Markdown\uFF09", "").action(
|