ai-project-manage-cli 7.1.20 → 7.1.23

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.
@@ -452,6 +452,116 @@ async function ensureRemoteBaselineBranch(cwd, baselineBranch) {
452
452
  `[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`
453
453
  );
454
454
  }
455
+ async function branchHasChangesAgainstBase(cwd, baseBranch, headBranch) {
456
+ const base = baseBranch.trim().replace(/^origin\//, "");
457
+ const head = headBranch.trim().replace(/^origin\//, "");
458
+ if (!base || !head) return false;
459
+ await execGit(cwd, ["fetch", "origin", base], true);
460
+ let headRef = head;
461
+ try {
462
+ await execGit(cwd, ["fetch", "origin", head], true);
463
+ await execGit(cwd, ["rev-parse", "--verify", `origin/${head}`], true);
464
+ headRef = `origin/${head}`;
465
+ } catch {
466
+ await execGit(cwd, ["rev-parse", "--verify", head], true);
467
+ }
468
+ const baseRef = `origin/${base}`;
469
+ const ahead = (await execGit(cwd, ["rev-list", "--count", `${baseRef}..${headRef}`], true)).trim();
470
+ if ((Number.parseInt(ahead, 10) || 0) <= 0) {
471
+ return false;
472
+ }
473
+ try {
474
+ await execGit(cwd, ["diff", "--quiet", `${baseRef}...${headRef}`], true);
475
+ return false;
476
+ } catch {
477
+ return true;
478
+ }
479
+ }
480
+ var FEATURE_BRANCH_RE = /^feat\/(task|session)-.+/;
481
+ function isFeatureBranchName(branch) {
482
+ return FEATURE_BRANCH_RE.test(branch.trim().replace(/^origin\//, ""));
483
+ }
484
+ async function resolveFeatureBranchBaseFromGit(cwd, headBranch) {
485
+ const head = headBranch.trim().replace(/^origin\//, "");
486
+ if (!head) {
487
+ throw new Error("[apm] head \u5206\u652F\u540D\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u63A8\u65AD\u57FA\u7EBF");
488
+ }
489
+ try {
490
+ const reflog = await execGit(
491
+ cwd,
492
+ ["reflog", "show", "--date=iso", head],
493
+ true
494
+ );
495
+ for (const line of reflog.split("\n")) {
496
+ const created = line.match(
497
+ /Created from (?:refs\/remotes\/origin\/|origin\/)?(\S+)/i
498
+ );
499
+ if (created?.[1]) {
500
+ const name = created[1].replace(/^origin\//, "");
501
+ if (name && name !== head && !isFeatureBranchName(name)) {
502
+ return name;
503
+ }
504
+ }
505
+ const moving = line.match(
506
+ /checkout: moving from (\S+) to (?:origin\/)?\S+/i
507
+ );
508
+ if (moving?.[1]) {
509
+ const from = moving[1].replace(/^origin\//, "");
510
+ if (from && from !== head && !isFeatureBranchName(from)) {
511
+ return from;
512
+ }
513
+ }
514
+ }
515
+ } catch {
516
+ }
517
+ await execGit(cwd, ["fetch", "origin", "--prune"], true);
518
+ let headRef = head;
519
+ try {
520
+ await execGit(cwd, ["rev-parse", "--verify", `origin/${head}`], true);
521
+ headRef = `origin/${head}`;
522
+ } catch {
523
+ await execGit(cwd, ["rev-parse", "--verify", head], true);
524
+ }
525
+ const refsOut = await execGit(
526
+ cwd,
527
+ ["for-each-ref", "--format=%(refname:short)", "refs/remotes/origin/"],
528
+ true
529
+ );
530
+ let best = null;
531
+ for (const ref of refsOut.split("\n").map((s) => s.trim()).filter(Boolean)) {
532
+ const name = ref.replace(/^origin\//, "");
533
+ if (!name || name === "HEAD" || name === head || isFeatureBranchName(name)) {
534
+ continue;
535
+ }
536
+ try {
537
+ await execGit(
538
+ cwd,
539
+ ["merge-base", "--is-ancestor", `origin/${name}`, headRef],
540
+ true
541
+ );
542
+ const ahead = Number.parseInt(
543
+ (await execGit(
544
+ cwd,
545
+ ["rev-list", "--count", `origin/${name}..${headRef}`],
546
+ true
547
+ )).trim(),
548
+ 10
549
+ );
550
+ if (!Number.isFinite(ahead) || ahead < 0) continue;
551
+ if (!best || ahead < best.ahead) {
552
+ best = { name, ahead };
553
+ }
554
+ } catch {
555
+ continue;
556
+ }
557
+ }
558
+ if (best) {
559
+ return best.name;
560
+ }
561
+ throw new Error(
562
+ `[apm] \u65E0\u6CD5\u63A8\u65AD ${head} \u57FA\u4E8E\u54EA\u6761\u5206\u652F\u521B\u5EFA\uFF0C\u8BF7\u786E\u8BA4\u672C\u5730 reflog \u6216 origin \u8FDC\u7A0B\u5206\u652F`
563
+ );
564
+ }
455
565
  async function hasUpstream(cwd) {
456
566
  try {
457
567
  await execGit(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], true);
@@ -3108,60 +3218,198 @@ function shouldEnsureWebIdePullRequests(action) {
3108
3218
  function shouldEnsureWebIdePullRequestsAfterAgent(action) {
3109
3219
  return POST_AGENT_PR_ACTIONS.has(action);
3110
3220
  }
3221
+ function formatWebIdePullRequestSummary(result) {
3222
+ const lines = [];
3223
+ lines.push("## PR \u521B\u5EFA\u7ED3\u679C");
3224
+ lines.push(
3225
+ `\u5408\u8BA1\uFF1A\u6210\u529F ${result.ensured} \u6761\uFF0C\u65E0\u53D8\u66F4\u8DF3\u8FC7 ${result.skippedNoChanges} \u6761\uFF0C\u5176\u4ED6\u8DF3\u8FC7 ${result.skipped} \u6761\uFF0C\u5931\u8D25 ${result.failed} \u6761\u3002`
3226
+ );
3227
+ if (result.items.length === 0) {
3228
+ lines.push("\u672A\u5904\u7406\u4EFB\u4F55\u4ED3\u5E93\u3002");
3229
+ return lines.join("\n");
3230
+ }
3231
+ lines.push("");
3232
+ for (const item of result.items) {
3233
+ if (item.outcome === "created") {
3234
+ const num = item.number != null && item.number > 0 ? `#${item.number}` : "\uFF08\u65E0\u7F16\u53F7\uFF09";
3235
+ const branch = item.headBranch && item.baseBranch ? `${item.headBranch} \u2192 ${item.baseBranch}` : item.baseBranch ? `\u76EE\u6807\u5206\u652F ${item.baseBranch}` : "";
3236
+ const state = item.state ? `\u72B6\u6001 ${item.state}` : "";
3237
+ const url = item.url?.trim() ? item.url.trim() : "";
3238
+ lines.push(
3239
+ `- **${item.label}**\uFF1A\u5DF2\u521B\u5EFA ${num}${branch ? `\uFF0C${branch}` : ""}${state ? `\uFF0C${state}` : ""}${url ? `\uFF0C${url}` : ""}`
3240
+ );
3241
+ continue;
3242
+ }
3243
+ if (item.outcome === "skipped_no_changes") {
3244
+ lines.push(
3245
+ `- **${item.label}**\uFF1A\u76F8\u5BF9\u57FA\u7EBF ${item.baseBranch || "\uFF08\u672A\u77E5\uFF09"} \u65E0\u4EE3\u7801\u53D8\u66F4\uFF0C\u672A\u521B\u5EFA PR`
3246
+ );
3247
+ continue;
3248
+ }
3249
+ if (item.outcome === "skipped") {
3250
+ lines.push(`- **${item.label}**\uFF1A\u5DF2\u8DF3\u8FC7 \u2014 ${item.detail}`);
3251
+ continue;
3252
+ }
3253
+ lines.push(`- **${item.label}**\uFF1A\u521B\u5EFA\u5931\u8D25 \u2014 ${item.detail}`);
3254
+ }
3255
+ return lines.join("\n");
3256
+ }
3111
3257
  async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3112
3258
  const api = createApmApiClient(cfg);
3259
+ const empty = () => ({
3260
+ ensured: 0,
3261
+ skipped: 0,
3262
+ skippedNoChanges: 0,
3263
+ failed: 0,
3264
+ items: [],
3265
+ tips: []
3266
+ });
3113
3267
  let repoRoots;
3114
3268
  try {
3115
3269
  const manifest = loadWorkspaceReposCache(workdir);
3116
3270
  repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
3117
3271
  } catch (err) {
3118
- console.warn(
3119
- "[apm] WebIDE PR\uFF1A\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25:",
3120
- err instanceof Error ? err.message : err
3121
- );
3122
- return { ensured: 0, skipped: 0, failed: 1 };
3272
+ const tip = `\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25: ${err instanceof Error ? err.message : err}`;
3273
+ console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3274
+ return {
3275
+ ...empty(),
3276
+ failed: 1,
3277
+ tips: [tip],
3278
+ items: [
3279
+ {
3280
+ label: "\u5DE5\u4F5C\u533A",
3281
+ outcome: "failed",
3282
+ detail: tip
3283
+ }
3284
+ ]
3285
+ };
3123
3286
  }
3287
+ const expectedHead = `feat/task-${taskId}`;
3124
3288
  console.log(
3125
- `[apm] WebIDE PR\uFF1A\u51C6\u5907\u4E3A ${repoRoots.length} \u4E2A\u4ED3\u5E93\u521B\u5EFA feat/task-${taskId}`
3289
+ `[apm] WebIDE PR\uFF1A\u51C6\u5907\u68C0\u67E5 ${repoRoots.length} \u4E2A\u4ED3\u5E93\uFF08\u671F\u671B\u7279\u6027\u5206\u652F ${expectedHead}\uFF09`
3126
3290
  );
3127
3291
  let ensured = 0;
3128
3292
  let skipped = 0;
3293
+ let skippedNoChanges = 0;
3129
3294
  let failed = 0;
3295
+ const tips = [];
3296
+ const items = [];
3130
3297
  for (const gitRoot of repoRoots) {
3131
3298
  const label = formatRepoLabel(workdir, gitRoot);
3132
3299
  try {
3300
+ const current = await getCurrentBranch(gitRoot);
3301
+ if (!isFeatureBranchName(current)) {
3302
+ const tip = `${label}\uFF1A\u5F53\u524D\u5206\u652F ${current} \u4E0D\u662F feat/task-* / feat/session-*\uFF0C\u4E0D\u7B26\u5408\u521B\u5EFA PR \u6761\u4EF6`;
3303
+ console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3304
+ tips.push(tip);
3305
+ skipped += 1;
3306
+ items.push({
3307
+ label,
3308
+ outcome: "skipped",
3309
+ headBranch: current,
3310
+ detail: tip
3311
+ });
3312
+ continue;
3313
+ }
3314
+ if (current !== expectedHead) {
3315
+ const tip = `${label}\uFF1A\u5F53\u524D\u4E3A ${current}\uFF0C\u671F\u671B ${expectedHead}\uFF0C\u5DF2\u8DF3\u8FC7`;
3316
+ console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3317
+ tips.push(tip);
3318
+ skipped += 1;
3319
+ items.push({
3320
+ label,
3321
+ outcome: "skipped",
3322
+ headBranch: current,
3323
+ detail: tip
3324
+ });
3325
+ continue;
3326
+ }
3327
+ const headBranch = current;
3133
3328
  const originUrl = await tryReadGitOriginUrl(gitRoot);
3134
3329
  if (!originUrl) {
3135
- console.warn(`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u65E0 remote.origin.url\uFF09`);
3330
+ const tip = `${label}\uFF1A\u65E0 remote.origin.url`;
3331
+ console.warn(`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${tip}`);
3332
+ tips.push(tip);
3136
3333
  skipped += 1;
3334
+ items.push({
3335
+ label,
3336
+ outcome: "skipped",
3337
+ headBranch,
3338
+ detail: tip
3339
+ });
3137
3340
  continue;
3138
3341
  }
3139
3342
  const matched = await api.cli.matchRepository({ url: originUrl });
3140
3343
  const repositoryId = matched.repositoryId?.trim();
3141
3344
  if (!repositoryId) {
3142
- console.warn(
3143
- `[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u5E73\u53F0\u672A\u767B\u8BB0\u4ED3\u5E93 ${originUrl}\uFF09`
3144
- );
3345
+ const tip = `${label}\uFF1A\u5E73\u53F0\u672A\u767B\u8BB0\u4ED3\u5E93 ${originUrl}`;
3346
+ console.warn(`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${tip}`);
3347
+ tips.push(tip);
3145
3348
  skipped += 1;
3349
+ items.push({
3350
+ label,
3351
+ outcome: "skipped",
3352
+ headBranch,
3353
+ detail: tip
3354
+ });
3355
+ continue;
3356
+ }
3357
+ const baseBranch = await resolveFeatureBranchBaseFromGit(
3358
+ gitRoot,
3359
+ headBranch
3360
+ );
3361
+ const hasChanges = await branchHasChangesAgainstBase(
3362
+ gitRoot,
3363
+ baseBranch,
3364
+ headBranch
3365
+ );
3366
+ if (!hasChanges) {
3367
+ const tip = `${label}\uFF1A\u76F8\u5BF9\u57FA\u7EBF ${baseBranch} \u65E0\u4EE3\u7801\u53D8\u66F4\uFF0C\u65E0\u9700\u521B\u5EFA PR`;
3368
+ console.log(`[apm] WebIDE PR\uFF1A${tip}`);
3369
+ tips.push(tip);
3370
+ skippedNoChanges += 1;
3371
+ items.push({
3372
+ label,
3373
+ outcome: "skipped_no_changes",
3374
+ headBranch,
3375
+ baseBranch,
3376
+ detail: tip
3377
+ });
3146
3378
  continue;
3147
3379
  }
3148
3380
  const pr = await api.cli.createWebIdeDraftPullRequest({
3149
3381
  taskId,
3150
- repositoryId
3382
+ repositoryId,
3383
+ headBranch,
3384
+ baseBranch
3151
3385
  });
3152
3386
  ensured += 1;
3387
+ items.push({
3388
+ label,
3389
+ outcome: "created",
3390
+ headBranch,
3391
+ baseBranch,
3392
+ number: pr.number,
3393
+ state: pr.state,
3394
+ url: pr.url,
3395
+ detail: `\u5DF2\u521B\u5EFA #${pr.number} ${headBranch} \u2192 ${baseBranch}`
3396
+ });
3153
3397
  console.log(
3154
- `[apm] WebIDE PR\uFF1A${label} \u2192 #${pr.number} ${pr.state} ${pr.url || ""}`
3398
+ `[apm] WebIDE PR\uFF1A${label} \u2192 #${pr.number} ${pr.state} ${headBranch} \u2192 ${baseBranch} ${pr.url || ""}`
3155
3399
  );
3156
3400
  } catch (err) {
3157
3401
  failed += 1;
3158
- console.warn(
3159
- `[apm] WebIDE PR\uFF1A${label} \u5931\u8D25:`,
3160
- err instanceof Error ? err.message : err
3161
- );
3402
+ const tip = `${label}\uFF1A${err instanceof Error ? err.message : err}`;
3403
+ console.warn(`[apm] WebIDE PR\uFF1A\u5931\u8D25 ${tip}`);
3404
+ tips.push(tip);
3405
+ items.push({
3406
+ label,
3407
+ outcome: "failed",
3408
+ detail: tip
3409
+ });
3162
3410
  }
3163
3411
  }
3164
- return { ensured, skipped, failed };
3412
+ return { ensured, skipped, skippedNoChanges, failed, items, tips };
3165
3413
  }
3166
3414
 
3167
3415
  // src/version.ts
@@ -3687,9 +3935,38 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3687
3935
  );
3688
3936
  }
3689
3937
  const prResult = await ensureWebIdePullRequests(cfg, taskId, workdir);
3938
+ if (prResult.tips.length > 0) {
3939
+ for (const tip of prResult.tips) {
3940
+ console.log(`[apm] webide create-pr \u63D0\u793A: ${tip}`);
3941
+ }
3942
+ }
3943
+ try {
3944
+ await appendContent(
3945
+ cfg,
3946
+ messageId,
3947
+ formatWebIdePullRequestSummary(prResult)
3948
+ );
3949
+ } catch (appendErr) {
3950
+ console.warn(
3951
+ "[apm] webide create-pr \u8FFD\u52A0 PR \u7ED3\u679C\u5931\u8D25:",
3952
+ appendErr instanceof Error ? appendErr.message : appendErr
3953
+ );
3954
+ }
3690
3955
  if (prResult.ensured === 0) {
3691
- const detail = prResult.failed > 0 ? `\u521B\u5EFA PR \u5931\u8D25\uFF08${prResult.failed} \u4E2A\u4ED3\u5E93\u51FA\u9519\uFF09` : "\u672A\u80FD\u521B\u5EFA PR\uFF1A\u672A\u5339\u914D\u5230\u53EF\u767B\u8BB0\u7684\u4ED3\u5E93\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u4F5C\u533A git remote \u4E0E\u5E73\u53F0\u4ED3\u5E93\u767B\u8BB0";
3692
- throw new Error(detail);
3956
+ if (prResult.failed > 0) {
3957
+ throw new Error(
3958
+ `\u521B\u5EFA PR \u5931\u8D25\uFF08${prResult.failed} \u4E2A\u4ED3\u5E93\u51FA\u9519\uFF09${prResult.tips[0] ? `\uFF1A${prResult.tips[0]}` : ""}`
3959
+ );
3960
+ }
3961
+ if (prResult.skippedNoChanges > 0 && prResult.skipped === 0) {
3962
+ console.log(
3963
+ "[apm] webide create-pr\uFF1A\u5404\u4ED3\u5E93\u76F8\u5BF9\u63A8\u65AD\u57FA\u7EBF\u5747\u65E0\u53D8\u66F4\uFF0C\u8DF3\u8FC7\u521B\u5EFA PR"
3964
+ );
3965
+ } else {
3966
+ throw new Error(
3967
+ prResult.tips[0] || "\u672A\u80FD\u521B\u5EFA PR\uFF1A\u8BF7\u786E\u8BA4\u5404\u4ED3\u5F53\u524D\u4F4D\u4E8E feat/task-* \u5206\u652F\uFF0C\u4E14 remote \u5DF2\u5728\u5E73\u53F0\u767B\u8BB0"
3968
+ );
3969
+ }
3693
3970
  }
3694
3971
  }
3695
3972
  if (projectId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "7.1.20",
3
+ "version": "7.1.23",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,