ai-project-manage-cli 6.0.80 → 6.0.82

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/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);
@@ -4238,6 +4265,42 @@ function posixBasename(p) {
4238
4265
  return idx >= 0 ? normalized.slice(idx + 1) : normalized;
4239
4266
  }
4240
4267
 
4268
+ // src/commands/deploy/internal/deploy-hints.ts
4269
+ function formatDeployNotConfiguredLines(env, deployCommands) {
4270
+ const configuredEnvs = Object.keys(deployCommands).filter(
4271
+ (key) => deployCommands[key]?.trim()
4272
+ );
4273
+ if (configuredEnvs.length > 0) {
4274
+ return [
4275
+ `[apm] \u65E0\u6CD5\u6267\u884C\u90E8\u7F72\uFF1Aapm.config.json \u4E2D\u672A\u914D\u7F6E deploy.${env}`,
4276
+ `[apm] \u5F53\u524D\u5DF2\u914D\u7F6E\u7684\u73AF\u5883: ${configuredEnvs.join(", ")}`
4277
+ ];
4278
+ }
4279
+ return [
4280
+ "[apm] \u65E0\u6CD5\u6267\u884C\u90E8\u7F72\uFF1A\u5F53\u524D\u9879\u76EE\u672A\u914D\u7F6E\u81EA\u52A8\u5316\u90E8\u7F72\uFF08apm.config.json \u4E2D\u65E0 deploy \u6BB5\uFF09",
4281
+ "[apm] \u90E8\u5206\u9879\u76EE\u4E0D\u652F\u6301\u6216\u672A\u542F\u7528\u81EA\u52A8\u5316\u90E8\u7F72\uFF0C\u5C5E\u6B63\u5E38\u60C5\u51B5\uFF0C\u53EF\u8DF3\u8FC7\u90E8\u7F72"
4282
+ ];
4283
+ }
4284
+ function formatWisdomFrontendDeployNotConfiguredLines(env, packageJsonPath) {
4285
+ return [
4286
+ `[apm] \u65E0\u6CD5\u6267\u884C\u90E8\u7F72\uFF1A${packageJsonPath} \u4E2D\u672A\u627E\u5230 deploy:${env} \u811A\u672C`,
4287
+ "[apm] \u82E5\u672C\u9879\u76EE\u4E0D\u652F\u6301\u81EA\u52A8\u5316\u90E8\u7F72\uFF0C\u5C5E\u6B63\u5E38\u60C5\u51B5\uFF0C\u53EF\u8DF3\u8FC7\u90E8\u7F72"
4288
+ ];
4289
+ }
4290
+ function printDeployNotConfiguredHint(env, deployCommands) {
4291
+ for (const line of formatDeployNotConfiguredLines(env, deployCommands)) {
4292
+ console.error(line);
4293
+ }
4294
+ }
4295
+ function printWisdomFrontendDeployNotConfiguredHint(env, packageJsonPath) {
4296
+ for (const line of formatWisdomFrontendDeployNotConfiguredLines(
4297
+ env,
4298
+ packageJsonPath
4299
+ )) {
4300
+ console.error(line);
4301
+ }
4302
+ }
4303
+
4241
4304
  // src/commands/deploy/internal/wisdom-auto-deploy.ts
4242
4305
  import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
4243
4306
  import path3 from "node:path";
@@ -5062,8 +5125,9 @@ async function runWisdomAutoDeploy(options) {
5062
5125
  if (projectType === "frontend") {
5063
5126
  const deployCmd = resolveFrontendDeployCommand(options.env, cwd);
5064
5127
  if (!deployCmd) {
5065
- console.error(
5066
- `\u524D\u7AEF\u9879\u76EE ${path3.join(cwd, "package.json")} \u4E2D\u672A\u627E\u5230 deploy:${options.env} \u811A\u672C`
5128
+ printWisdomFrontendDeployNotConfiguredHint(
5129
+ options.env,
5130
+ path3.join(cwd, "package.json")
5067
5131
  );
5068
5132
  process.exit(1);
5069
5133
  }
@@ -5115,12 +5179,7 @@ function registerDeployMainCommand(program) {
5115
5179
  const deployCommands = cfg.deploy ?? {};
5116
5180
  const command = deployCommands[env]?.trim();
5117
5181
  if (!command) {
5118
- const keys = Object.keys(deployCommands);
5119
- console.error(
5120
- keys.length > 0 ? `apm.config.json \u4E2D\u672A\u914D\u7F6E deploy.${env}\uFF0C\u53EF\u7528\u73AF\u5883: ${keys.join(
5121
- ", "
5122
- )}` : `apm.config.json \u4E2D\u672A\u914D\u7F6E deploy \u6BB5`
5123
- );
5182
+ printDeployNotConfiguredHint(env, deployCommands);
5124
5183
  process.exit(1);
5125
5184
  }
5126
5185
  runShellCommand3(command, cwd);
@@ -6147,17 +6206,24 @@ function buildProgram() {
6147
6206
  lines: Number.isFinite(lines) && lines > 0 ? lines : 100
6148
6207
  });
6149
6208
  });
6150
- program.command("branch").description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
6209
+ const branch = program.command("branch").description("\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>");
6210
+ branch.command("prune").description(
6211
+ "\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"
6212
+ ).option("--dry-run", "\u4EC5\u5217\u51FA\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").option(
6213
+ "-A, --all",
6214
+ "\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"
6215
+ ).action(async (opts) => {
6216
+ await runCleanBranches({
6217
+ dryRun: opts.dryRun,
6218
+ includeRemote: opts.all
6219
+ });
6220
+ });
6221
+ branch.description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
6151
6222
  "-m, --message <text>",
6152
6223
  "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E"
6153
6224
  ).action(async (sessionId, opts) => {
6154
6225
  await runBranch(sessionId, { message: opts.message });
6155
6226
  });
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
6227
  program.command("create-pr").description(
6162
6228
  "\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
6229
  ).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.82",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,8 +1,14 @@
1
+ # apm-deploy:自动化部署
2
+
1
3
  ## 工作流程
2
4
 
3
- 在项目根目录执行 `apm deploy <环境名称>`
5
+ 在项目根目录执行 `apm deploy <环境名称>`,**以命令执行结果为准**:
6
+
7
+ - **成功**:部署完成,在回信中说明测试环境地址或部署结果。
8
+ - **失败且输出含「未配置自动化部署」或「可跳过部署」**:本项目不支持自动化部署,属正常情况;放弃部署,在回信说明已跳过即可,无需修复配置或重试。
9
+ - **其他失败**:按输出排查并修复后重试。
4
10
 
5
11
  ## 示例
6
12
 
7
13
  1. 部署测试环境:`apm deploy test`
8
- 2. 部署正式环境: `apm deploy online`
14
+ 2. 部署正式环境:`apm deploy online`
@@ -86,7 +86,7 @@ ALTER TABLE inspection_class ADD COLUMN is_project_add VARCHAR(1) DEFAULT '0';
86
86
  开发完成的定义是以下三项**全部满足**,缺一不可:
87
87
 
88
88
  1. **构建通过**:执行本仓库的构建/检查命令(见 AGENTS.md 或部署文档),失败必须修复后重试。
89
- 2. **发布测试环境**:**Read** `.apm/skills/apm-deploy/SKILL.md` 并按其流程部署;**后端**涉及 SQL 变更时,须在回复中引用 `docs/SQL.md`,并写明待执行的 SQL 文件名或执行顺序。
89
+ 2. **发布测试环境**:**Read** `.apm/skills/apm-deploy/SKILL.md` 并按其流程执行 `apm deploy`(以命令结果判断是否跳过);**后端**涉及 SQL 变更时,须在回复中引用 `docs/SQL.md`,并写明待执行的 SQL 文件名或执行顺序。
90
90
  3. **白名单对账**:在回复中逐文件列出本次改动与计划白名单的对应关系。
91
91
 
92
92
  **注意:不做联调。** 前后端各自按 `API.md` 交付,接口对不上属于契约或实现问题,由 diff 评审与人工验收暴露后打回修复;禁止自行发起「联调」「接口实测」类的开放式动作。