ai-project-manage-cli 6.0.80 → 6.0.81

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 (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +66 -32
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -31,6 +31,7 @@ apm sync-project-documents
31
31
  apm sync-project-documents --push
32
32
  apm update-skills
33
33
  apm branch <sessionId>
34
+ apm branch prune [--dry-run] [-A|--all]
34
35
  ```
35
36
 
36
37
  ## 常驻连接(后台守护)
package/dist/index.js CHANGED
@@ -1180,22 +1180,47 @@ async function ensureGitRepo2(cwd) {
1180
1180
  async function getCurrentBranch2(cwd) {
1181
1181
  return (await execGit3(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1182
1182
  }
1183
- async function resolveDefaultBranch(cwd) {
1183
+ async function resolveBaselineBranch(cwd, api) {
1184
+ const workdirPath = resolveWorkdirPath(cwd);
1185
+ const baseline = await api.cli.workspaceBaseline({ workdirPath });
1186
+ if (!baseline.repositoryId) {
1187
+ 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
1188
+ \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
1189
+ throw new Error(`[apm] ${detail}`);
1190
+ }
1191
+ const baselineBranch = (baseline.defaultBranch ?? "").trim();
1192
+ if (!baselineBranch) {
1193
+ throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
1194
+ }
1195
+ await execGit3(cwd, ["fetch", "origin", baselineBranch], true);
1196
+ if (!await remoteBranchExists(cwd, baselineBranch)) {
1197
+ throw new Error(
1198
+ `[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`
1199
+ );
1200
+ }
1201
+ return baselineBranch;
1202
+ }
1203
+ async function isBranchBasedOnBaseline(cwd, branch, baselineBranch) {
1204
+ const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
1184
1205
  try {
1185
- const ref = (await execGit3(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], true)).trim();
1186
- const match = ref.match(/^refs\/remotes\/origin\/(.+)$/);
1187
- if (match?.[1]) {
1188
- return match[1];
1189
- }
1206
+ await execGit3(
1207
+ cwd,
1208
+ ["merge-base", "--is-ancestor", `origin/${baselineBranch}`, ref],
1209
+ true
1210
+ );
1211
+ return true;
1190
1212
  } catch {
1213
+ return false;
1191
1214
  }
1192
- const out = await execGit3(cwd, ["remote", "show", "origin"], true);
1193
- const headLine = out.split(/\r?\n/).find((line) => line.includes("HEAD branch"));
1194
- const branch = headLine?.split(":").pop()?.trim();
1195
- if (branch) {
1196
- return branch;
1215
+ }
1216
+ async function listCandidateSessionBranches(cwd, includeRemote) {
1217
+ const names = new Set(await listLocalSessionBranches(cwd));
1218
+ if (includeRemote) {
1219
+ for (const branch of await listRemoteSessionBranches(cwd)) {
1220
+ names.add(branch);
1221
+ }
1197
1222
  }
1198
- throw new Error("[apm] \u65E0\u6CD5\u89E3\u6790 origin \u9ED8\u8BA4\u5206\u652F\uFF0C\u8BF7\u5148\u6267\u884C git fetch origin");
1223
+ return [...names].sort((a, b) => a.localeCompare(b));
1199
1224
  }
1200
1225
  async function listLocalSessionBranches(cwd) {
1201
1226
  const out = await execGit3(
@@ -1268,20 +1293,27 @@ async function isBranchMergedIntoDefault(cwd, branch, defaultBranch) {
1268
1293
  async function runCleanBranches(options = {}) {
1269
1294
  const cwd = options.cwd ?? process.cwd();
1270
1295
  const dryRun = options.dryRun ?? false;
1296
+ const includeRemote = options.includeRemote ?? false;
1271
1297
  await ensureGitRepo2(cwd);
1272
1298
  await execGit3(cwd, ["fetch", "--prune", "origin"], true);
1273
1299
  const cfg = await ensureLoggedConfig();
1274
1300
  const api = createApmApiClient(cfg);
1301
+ const baselineBranch = await resolveBaselineBranch(cwd, api);
1275
1302
  const { sessions } = await api.cli.listSessionsForBranchCleanup({});
1276
1303
  const sessionStatusById = new Map(
1277
1304
  sessions.map((item) => [item.sessionId, item.taskStatus])
1278
1305
  );
1279
- const branchNames = /* @__PURE__ */ new Set([
1280
- ...await listLocalSessionBranches(cwd),
1281
- ...await listRemoteSessionBranches(cwd)
1282
- ]);
1283
- if (branchNames.size === 0) {
1284
- console.log("[apm] \u672A\u53D1\u73B0 feat/session-* \u5206\u652F");
1306
+ const candidates = await listCandidateSessionBranches(cwd, includeRemote);
1307
+ const branchNames = [];
1308
+ for (const branch of candidates) {
1309
+ if (await isBranchBasedOnBaseline(cwd, branch, baselineBranch)) {
1310
+ branchNames.push(branch);
1311
+ }
1312
+ }
1313
+ if (branchNames.length === 0) {
1314
+ console.log(
1315
+ `[apm] \u672A\u53D1\u73B0\u4ECE ${baselineBranch} \u884D\u751F\u7684\u672C\u5730 feat/session-* \u5206\u652F${includeRemote ? "\uFF08\u542B\u8FDC\u7A0B\uFF09" : ""}`
1316
+ );
1285
1317
  return;
1286
1318
  }
1287
1319
  const toDelete = [...branchNames].map((branch) => {
@@ -1300,10 +1332,6 @@ async function runCleanBranches(options = {}) {
1300
1332
  return;
1301
1333
  }
1302
1334
  let currentBranch = await getCurrentBranch2(cwd);
1303
- let defaultBranch = null;
1304
- if (dryRun) {
1305
- defaultBranch = await resolveDefaultBranch(cwd);
1306
- }
1307
1335
  for (const item of toDelete) {
1308
1336
  const { branch, sessionId, reason } = item;
1309
1337
  const label = `${branch} (${sessionId}: ${reason})`;
@@ -1311,16 +1339,15 @@ async function runCleanBranches(options = {}) {
1311
1339
  const merged = await isBranchMergedIntoDefault(
1312
1340
  cwd,
1313
1341
  branch,
1314
- defaultBranch
1342
+ baselineBranch
1315
1343
  );
1316
1344
  const mergeTag = merged ? "\u5DF2\u5408\u5E76" : "\u672A\u5408\u5E76";
1317
1345
  console.log(`[apm] [dry-run] \u5C06\u5220\u9664 ${label} [${mergeTag}]`);
1318
1346
  continue;
1319
1347
  }
1320
1348
  if (currentBranch === branch) {
1321
- defaultBranch ??= await resolveDefaultBranch(cwd);
1322
- await execGit3(cwd, ["checkout", defaultBranch], true);
1323
- currentBranch = defaultBranch;
1349
+ await execGit3(cwd, ["checkout", baselineBranch], true);
1350
+ currentBranch = baselineBranch;
1324
1351
  }
1325
1352
  if (await localBranchExists2(cwd, branch)) {
1326
1353
  await execGit3(cwd, ["branch", "-D", branch], true);
@@ -6147,17 +6174,24 @@ function buildProgram() {
6147
6174
  lines: Number.isFinite(lines) && lines > 0 ? lines : 100
6148
6175
  });
6149
6176
  });
6150
- program.command("branch").description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
6177
+ const branch = program.command("branch").description("\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>");
6178
+ branch.command("prune").description(
6179
+ "\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"
6180
+ ).option("--dry-run", "\u4EC5\u5217\u51FA\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").option(
6181
+ "-A, --all",
6182
+ "\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"
6183
+ ).action(async (opts) => {
6184
+ await runCleanBranches({
6185
+ dryRun: opts.dryRun,
6186
+ includeRemote: opts.all
6187
+ });
6188
+ });
6189
+ branch.description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
6151
6190
  "-m, --message <text>",
6152
6191
  "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E"
6153
6192
  ).action(async (sessionId, opts) => {
6154
6193
  await runBranch(sessionId, { message: opts.message });
6155
6194
  });
6156
- program.command("clean-branches").description(
6157
- "\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"
6158
- ).option("--dry-run", "\u4EC5\u5217\u51FA\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (opts) => {
6159
- await runCleanBranches({ dryRun: opts.dryRun });
6160
- });
6161
6195
  program.command("create-pr").description(
6162
6196
  "\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"
6163
6197
  ).requiredOption("--session <sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--title <title>", "PR \u6807\u9898").option("--content <content>", "PR \u6B63\u6587\uFF08Markdown\uFF09", "").action(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.80",
3
+ "version": "6.0.81",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,