ai-project-manage-cli 7.1.10 → 7.1.12

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 CHANGED
@@ -505,6 +505,10 @@ var requestConfig = {
505
505
  method: "POST",
506
506
  path: "/cli/webide/draft-pull-requests"
507
507
  }),
508
+ webideMergePullRequests: defineEndpoint({
509
+ method: "POST",
510
+ path: "/cli/webide/pull-requests/merge"
511
+ }),
508
512
  getRepositoryProjectDocumentManifest: defineEndpoint({
509
513
  method: "GET",
510
514
  path: "/cli/repository-project-documents/manifest"
@@ -648,6 +652,22 @@ async function hasUpstream(cwd) {
648
652
  return false;
649
653
  }
650
654
  }
655
+ async function pushCurrentBranch(cwd) {
656
+ if (!await isGitRepo(cwd)) {
657
+ return false;
658
+ }
659
+ const originUrl = await tryReadGitOriginUrl(cwd);
660
+ if (!originUrl) {
661
+ console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8DF3\u8FC7 push");
662
+ return false;
663
+ }
664
+ if (await hasUpstream(cwd)) {
665
+ await execGit(cwd, ["push"]);
666
+ } else {
667
+ await execGit(cwd, ["push", "-u", "origin", "HEAD"]);
668
+ }
669
+ return true;
670
+ }
651
671
  var GITIGNORE_COMMIT_MESSAGE = "chore(apm): ignore .apm directory";
652
672
  async function commitAndPushGitignore(workdir) {
653
673
  if (!await isGitRepo(workdir)) {
@@ -657,17 +677,12 @@ async function commitAndPushGitignore(workdir) {
657
677
  await execGit(workdir, ["add", "--", ".gitignore"]);
658
678
  await execGit(workdir, ["commit", "-m", GITIGNORE_COMMIT_MESSAGE]);
659
679
  console.log(`[apm] \u5DF2\u63D0\u4EA4 .gitignore: ${GITIGNORE_COMMIT_MESSAGE}`);
660
- const originUrl = await tryReadGitOriginUrl(workdir);
661
- if (!originUrl) {
662
- console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
663
- return;
664
- }
665
- if (await hasUpstream(workdir)) {
666
- await execGit(workdir, ["push"]);
680
+ const pushed = await pushCurrentBranch(workdir);
681
+ if (pushed) {
682
+ console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
667
683
  } else {
668
- await execGit(workdir, ["push", "-u", "origin", "HEAD"]);
684
+ console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
669
685
  }
670
- console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
671
686
  }
672
687
 
673
688
  // src/baseline-resolve.ts
@@ -6700,7 +6715,7 @@ async function syncCursorMessageLog(cfg, ctx, events) {
6700
6715
  function createAppendMessageCustomTools(cfg, messageId, appendContent) {
6701
6716
  const doAppend = appendContent ?? ((content) => appendMessageContent(cfg, messageId, content));
6702
6717
  return {
6703
- append_message: {
6718
+ AppendMessage: {
6704
6719
  description: "\u5411\u5F53\u524D\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8865\u5145\u8FDB\u5C55\uFF1B\u88AB @ \u65F6\u6536\u5230\u540E\u5E94\u5148\u7B80\u77ED\u786E\u8BA4\u518D\u6267\u884C\u4EFB\u52A1\u3002",
6705
6720
  inputSchema: {
6706
6721
  type: "object",
@@ -6722,7 +6737,7 @@ function createAppendMessageCustomTools(cfg, messageId, appendContent) {
6722
6737
  }
6723
6738
  try {
6724
6739
  await doAppend(content);
6725
- console.log(`[apm] append_message \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
6740
+ console.log(`[apm] AppendMessage \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
6726
6741
  return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
6727
6742
  } catch (err) {
6728
6743
  const detail = err instanceof Error ? err.message : String(err);
@@ -6790,6 +6805,35 @@ ${payload}`;
6790
6805
  };
6791
6806
  }
6792
6807
 
6808
+ // src/commands/connect/webide-merge-tools.ts
6809
+ function createMergeWebIdePullRequestsTool(options) {
6810
+ const { cfg, taskId } = options;
6811
+ return {
6812
+ description: "Merge all open pull requests for this WebIDE task after conflicts are resolved locally and pushed. Call once with no arguments. Do NOT call if conflicts contradict the task requirements \u2014 report the conflict and fail instead.",
6813
+ inputSchema: {
6814
+ type: "object",
6815
+ properties: {
6816
+ confirmedConflictFree: {
6817
+ type: "boolean",
6818
+ description: "Must be true: you have checked each repo, resolved mergeable conflicts consistently with requirements, and pushed. If conflicts contradict requirements, do not call this tool."
6819
+ }
6820
+ },
6821
+ required: ["confirmedConflictFree"]
6822
+ },
6823
+ execute: async (args) => {
6824
+ if (args.confirmedConflictFree !== true) {
6825
+ throw new Error(
6826
+ "MergeWebIdePullRequests \u8981\u6C42 confirmedConflictFree=true\u3002\u82E5\u5B58\u5728\u4E0E\u9700\u6C42\u76F8\u6096\u7684\u51B2\u7A81\uFF0C\u8BF7\u52FF\u8C03\u7528\u672C\u5DE5\u5177\uFF0C\u7528 AppendMessage \u8BF4\u660E\u5E76\u5931\u8D25\u7ED3\u675F\u3002"
6827
+ );
6828
+ }
6829
+ const api = createApmApiClient(cfg);
6830
+ console.log(`[apm] MergeWebIdePullRequests taskId=${taskId}`);
6831
+ const result = await api.cli.webideMergePullRequests({ taskId });
6832
+ return JSON.stringify(result, null, 2);
6833
+ }
6834
+ };
6835
+ }
6836
+
6793
6837
  // src/commands/connect/webide-plan-tools.ts
6794
6838
  function asString(value) {
6795
6839
  return typeof value === "string" ? value.trim() : "";
@@ -6977,6 +7021,10 @@ function createCursorCustomTools(cfg, messageId, options) {
6977
7021
  cfg,
6978
7022
  taskId: options.taskId
6979
7023
  });
7024
+ tools.MergeWebIdePullRequests = createMergeWebIdePullRequestsTool({
7025
+ cfg,
7026
+ taskId: options.taskId
7027
+ });
6980
7028
  }
6981
7029
  return tools;
6982
7030
  }
@@ -136,6 +136,10 @@ var requestConfig = {
136
136
  method: "POST",
137
137
  path: "/cli/webide/draft-pull-requests"
138
138
  }),
139
+ webideMergePullRequests: defineEndpoint({
140
+ method: "POST",
141
+ path: "/cli/webide/pull-requests/merge"
142
+ }),
139
143
  getRepositoryProjectDocumentManifest: defineEndpoint({
140
144
  method: "GET",
141
145
  path: "/cli/repository-project-documents/manifest"
@@ -381,6 +385,22 @@ async function hasUpstream(cwd) {
381
385
  return false;
382
386
  }
383
387
  }
388
+ async function pushCurrentBranch(cwd) {
389
+ if (!await isGitRepo(cwd)) {
390
+ return false;
391
+ }
392
+ const originUrl = await tryReadGitOriginUrl(cwd);
393
+ if (!originUrl) {
394
+ console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8DF3\u8FC7 push");
395
+ return false;
396
+ }
397
+ if (await hasUpstream(cwd)) {
398
+ await execGit(cwd, ["push"]);
399
+ } else {
400
+ await execGit(cwd, ["push", "-u", "origin", "HEAD"]);
401
+ }
402
+ return true;
403
+ }
384
404
  var GITIGNORE_COMMIT_MESSAGE = "chore(apm): ignore .apm directory";
385
405
  async function commitAndPushGitignore(workdir) {
386
406
  if (!await isGitRepo(workdir)) {
@@ -390,17 +410,12 @@ async function commitAndPushGitignore(workdir) {
390
410
  await execGit(workdir, ["add", "--", ".gitignore"]);
391
411
  await execGit(workdir, ["commit", "-m", GITIGNORE_COMMIT_MESSAGE]);
392
412
  console.log(`[apm] \u5DF2\u63D0\u4EA4 .gitignore: ${GITIGNORE_COMMIT_MESSAGE}`);
393
- const originUrl = await tryReadGitOriginUrl(workdir);
394
- if (!originUrl) {
395
- console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
396
- return;
397
- }
398
- if (await hasUpstream(workdir)) {
399
- await execGit(workdir, ["push"]);
413
+ const pushed = await pushCurrentBranch(workdir);
414
+ if (pushed) {
415
+ console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
400
416
  } else {
401
- await execGit(workdir, ["push", "-u", "origin", "HEAD"]);
417
+ console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
402
418
  }
403
- console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
404
419
  }
405
420
 
406
421
  // src/baseline-resolve.ts
@@ -839,6 +854,36 @@ async function commitWorkingTreeIfDirty(cwd, message) {
839
854
  console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
840
855
  return true;
841
856
  }
857
+ async function commitWorkspaceReposIfDirty(workdir, message) {
858
+ const workdirPath = resolveWorkdirPath(workdir);
859
+ const manifest = loadWorkspaceReposCache(workdirPath);
860
+ const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
861
+ let committed = 0;
862
+ for (const gitRoot of repoRoots) {
863
+ const label = formatRepoLabel(workdirPath, gitRoot);
864
+ const did = await commitWorkingTreeIfDirty(gitRoot, message);
865
+ if (did) {
866
+ committed += 1;
867
+ console.log(`[apm] WebIDE \u515C\u5E95\u63D0\u4EA4 @ ${label}`);
868
+ }
869
+ }
870
+ return committed;
871
+ }
872
+ async function pushWorkspaceRepos(workdir) {
873
+ const workdirPath = resolveWorkdirPath(workdir);
874
+ const manifest = loadWorkspaceReposCache(workdirPath);
875
+ const repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
876
+ let pushed = 0;
877
+ for (const gitRoot of repoRoots) {
878
+ const label = formatRepoLabel(workdirPath, gitRoot);
879
+ const did = await pushCurrentBranch(gitRoot);
880
+ if (did) {
881
+ pushed += 1;
882
+ console.log(`[apm] WebIDE \u5DF2 push @ ${label}`);
883
+ }
884
+ }
885
+ return pushed;
886
+ }
842
887
  function branchNameForTask(taskId) {
843
888
  const id = taskId.trim();
844
889
  if (!id) {
@@ -1420,7 +1465,7 @@ async function appendMessageContent(cfg, messageId, content) {
1420
1465
  function createAppendMessageCustomTools(cfg, messageId, appendContent2) {
1421
1466
  const doAppend = appendContent2 ?? ((content) => appendMessageContent(cfg, messageId, content));
1422
1467
  return {
1423
- append_message: {
1468
+ AppendMessage: {
1424
1469
  description: "\u5411\u5F53\u524D\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8865\u5145\u8FDB\u5C55\uFF1B\u88AB @ \u65F6\u6536\u5230\u540E\u5E94\u5148\u7B80\u77ED\u786E\u8BA4\u518D\u6267\u884C\u4EFB\u52A1\u3002",
1425
1470
  inputSchema: {
1426
1471
  type: "object",
@@ -1442,7 +1487,7 @@ function createAppendMessageCustomTools(cfg, messageId, appendContent2) {
1442
1487
  }
1443
1488
  try {
1444
1489
  await doAppend(content);
1445
- console.log(`[apm] append_message \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
1490
+ console.log(`[apm] AppendMessage \u5DF2\u8FFD\u52A0: messageId=${messageId}`);
1446
1491
  return "\u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9";
1447
1492
  } catch (err) {
1448
1493
  const detail = err instanceof Error ? err.message : String(err);
@@ -1510,6 +1555,35 @@ ${payload}`;
1510
1555
  };
1511
1556
  }
1512
1557
 
1558
+ // src/commands/connect/webide-merge-tools.ts
1559
+ function createMergeWebIdePullRequestsTool(options) {
1560
+ const { cfg, taskId } = options;
1561
+ return {
1562
+ description: "Merge all open pull requests for this WebIDE task after conflicts are resolved locally and pushed. Call once with no arguments. Do NOT call if conflicts contradict the task requirements \u2014 report the conflict and fail instead.",
1563
+ inputSchema: {
1564
+ type: "object",
1565
+ properties: {
1566
+ confirmedConflictFree: {
1567
+ type: "boolean",
1568
+ description: "Must be true: you have checked each repo, resolved mergeable conflicts consistently with requirements, and pushed. If conflicts contradict requirements, do not call this tool."
1569
+ }
1570
+ },
1571
+ required: ["confirmedConflictFree"]
1572
+ },
1573
+ execute: async (args) => {
1574
+ if (args.confirmedConflictFree !== true) {
1575
+ throw new Error(
1576
+ "MergeWebIdePullRequests \u8981\u6C42 confirmedConflictFree=true\u3002\u82E5\u5B58\u5728\u4E0E\u9700\u6C42\u76F8\u6096\u7684\u51B2\u7A81\uFF0C\u8BF7\u52FF\u8C03\u7528\u672C\u5DE5\u5177\uFF0C\u7528 AppendMessage \u8BF4\u660E\u5E76\u5931\u8D25\u7ED3\u675F\u3002"
1577
+ );
1578
+ }
1579
+ const api = createApmApiClient(cfg);
1580
+ console.log(`[apm] MergeWebIdePullRequests taskId=${taskId}`);
1581
+ const result = await api.cli.webideMergePullRequests({ taskId });
1582
+ return JSON.stringify(result, null, 2);
1583
+ }
1584
+ };
1585
+ }
1586
+
1513
1587
  // src/commands/connect/webide-plan-tools.ts
1514
1588
  function asString(value) {
1515
1589
  return typeof value === "string" ? value.trim() : "";
@@ -1697,6 +1771,10 @@ function createCursorCustomTools(cfg, messageId, options) {
1697
1771
  cfg,
1698
1772
  taskId: options.taskId
1699
1773
  });
1774
+ tools.MergeWebIdePullRequests = createMergeWebIdePullRequestsTool({
1775
+ cfg,
1776
+ taskId: options.taskId
1777
+ });
1700
1778
  }
1701
1779
  return tools;
1702
1780
  }
@@ -2386,13 +2464,13 @@ function normalizeLocalDocumentPath(path) {
2386
2464
  return segments.join("/");
2387
2465
  }
2388
2466
  function readLocalManifest(apmRoot) {
2389
- const manifestPath2 = join6(projectDocumentsDir(apmRoot), MANIFEST_FILE);
2390
- if (!existsSync5(manifestPath2)) {
2467
+ const manifestPath3 = join6(projectDocumentsDir(apmRoot), MANIFEST_FILE);
2468
+ if (!existsSync5(manifestPath3)) {
2391
2469
  return null;
2392
2470
  }
2393
2471
  try {
2394
2472
  return JSON.parse(
2395
- readFileSync6(manifestPath2, "utf8")
2473
+ readFileSync6(manifestPath3, "utf8")
2396
2474
  );
2397
2475
  } catch {
2398
2476
  return null;
@@ -2544,12 +2622,17 @@ function markBranchDone(sessionId, workdir) {
2544
2622
  lastBranchKey = sessionWorkdirKey(sessionId, workdir);
2545
2623
  }
2546
2624
 
2547
- // src/commands/connect/webide-draft-prs.ts
2548
- var DEVELOP_ACTIONS = /* @__PURE__ */ new Set(["start-develop", "skip-plan"]);
2549
- function shouldEnsureWebIdeDraftPullRequests(action) {
2550
- return DEVELOP_ACTIONS.has(action);
2551
- }
2552
- async function ensureWebIdeDraftPullRequests(cfg, taskId, workdir) {
2625
+ // src/commands/connect/webide-pull-requests.ts
2626
+ var TEST_START_ACTIONS = /* @__PURE__ */ new Set([
2627
+ "generate-cases",
2628
+ "run-auto-test",
2629
+ "skip-test",
2630
+ "enter-manual-test"
2631
+ ]);
2632
+ function shouldEnsureWebIdePullRequests(action) {
2633
+ return TEST_START_ACTIONS.has(action);
2634
+ }
2635
+ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
2553
2636
  const api = createApmApiClient(cfg);
2554
2637
  let repoRoots;
2555
2638
  try {
@@ -2557,29 +2640,27 @@ async function ensureWebIdeDraftPullRequests(cfg, taskId, workdir) {
2557
2640
  repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
2558
2641
  } catch (err) {
2559
2642
  console.warn(
2560
- "[apm] WebIDE \u8349\u7A3F PR\uFF1A\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25:",
2643
+ "[apm] WebIDE PR\uFF1A\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25:",
2561
2644
  err instanceof Error ? err.message : err
2562
2645
  );
2563
2646
  return;
2564
2647
  }
2565
2648
  console.log(
2566
- `[apm] WebIDE \u8349\u7A3F PR\uFF1A\u51C6\u5907\u4E3A ${repoRoots.length} \u4E2A\u4ED3\u5E93\u521B\u5EFA feat/task-${taskId}`
2649
+ `[apm] WebIDE PR\uFF1A\u51C6\u5907\u4E3A ${repoRoots.length} \u4E2A\u4ED3\u5E93\u521B\u5EFA feat/task-${taskId}`
2567
2650
  );
2568
2651
  for (const gitRoot of repoRoots) {
2569
2652
  const label = formatRepoLabel(workdir, gitRoot);
2570
2653
  try {
2571
2654
  const originUrl = await tryReadGitOriginUrl(gitRoot);
2572
2655
  if (!originUrl) {
2573
- console.warn(
2574
- `[apm] WebIDE \u8349\u7A3F PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u65E0 remote.origin.url\uFF09`
2575
- );
2656
+ console.warn(`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u65E0 remote.origin.url\uFF09`);
2576
2657
  continue;
2577
2658
  }
2578
2659
  const matched = await api.cli.matchRepository({ url: originUrl });
2579
2660
  const repositoryId = matched.repositoryId?.trim();
2580
2661
  if (!repositoryId) {
2581
2662
  console.warn(
2582
- `[apm] WebIDE \u8349\u7A3F PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u5E73\u53F0\u672A\u767B\u8BB0\u4ED3\u5E93 ${originUrl}\uFF09`
2663
+ `[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u5E73\u53F0\u672A\u767B\u8BB0\u4ED3\u5E93 ${originUrl}\uFF09`
2583
2664
  );
2584
2665
  continue;
2585
2666
  }
@@ -2588,18 +2669,297 @@ async function ensureWebIdeDraftPullRequests(cfg, taskId, workdir) {
2588
2669
  repositoryId
2589
2670
  });
2590
2671
  console.log(
2591
- `[apm] WebIDE \u8349\u7A3F PR\uFF1A${label} \u2192 #${pr.number} ${pr.state} ${pr.url || ""}`
2672
+ `[apm] WebIDE PR\uFF1A${label} \u2192 #${pr.number} ${pr.state} ${pr.url || ""}`
2592
2673
  );
2593
2674
  } catch (err) {
2594
2675
  console.warn(
2595
- `[apm] WebIDE \u8349\u7A3F PR\uFF1A${label} \u5931\u8D25:`,
2676
+ `[apm] WebIDE PR\uFF1A${label} \u5931\u8D25:`,
2596
2677
  err instanceof Error ? err.message : err
2597
2678
  );
2598
2679
  }
2599
2680
  }
2600
2681
  }
2601
2682
 
2683
+ // src/commands/connect/webide-baseline-sync.ts
2684
+ async function abortMergeOrRebase(cwd) {
2685
+ try {
2686
+ await execGit(cwd, ["merge", "--abort"], true);
2687
+ } catch {
2688
+ }
2689
+ try {
2690
+ await execGit(cwd, ["rebase", "--abort"], true);
2691
+ } catch {
2692
+ }
2693
+ }
2694
+ async function syncWebIdeBaselineOrFail(workdir) {
2695
+ let repoRoots;
2696
+ try {
2697
+ const manifest = loadWorkspaceReposCache(workdir);
2698
+ repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
2699
+ } catch (err) {
2700
+ return {
2701
+ ok: false,
2702
+ error: `\u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`
2703
+ };
2704
+ }
2705
+ const failures = [];
2706
+ for (const repoRoot of repoRoots) {
2707
+ const label = formatRepoLabel(workdir, repoRoot);
2708
+ try {
2709
+ const gitRoot = await resolveGitRepoRoot(repoRoot);
2710
+ const baselineBranch = await resolveDefaultRemoteBranch(gitRoot);
2711
+ await ensureRemoteBaselineBranch(gitRoot, baselineBranch);
2712
+ const currentBranch = await getCurrentBranch(gitRoot);
2713
+ console.log(
2714
+ `[apm] webide accept sync ${label}: ${currentBranch} \u2190 origin/${baselineBranch}`
2715
+ );
2716
+ try {
2717
+ await execGit(
2718
+ gitRoot,
2719
+ ["merge", `origin/${baselineBranch}`, "--no-edit"],
2720
+ true
2721
+ );
2722
+ } catch (mergeErr) {
2723
+ await abortMergeOrRebase(gitRoot);
2724
+ const detail = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
2725
+ failures.push(
2726
+ `${label}\uFF1A\u5408\u5E76 origin/${baselineBranch} \u51B2\u7A81\uFF0C\u5DF2\u6267\u884C merge --abort \u64A4\u56DE\u672C\u6B21\u540C\u6B65\u3002${detail}`
2727
+ );
2728
+ }
2729
+ } catch (err) {
2730
+ failures.push(
2731
+ `${label}\uFF1A${err instanceof Error ? err.message : String(err)}`
2732
+ );
2733
+ }
2734
+ }
2735
+ if (failures.length > 0) {
2736
+ return {
2737
+ ok: false,
2738
+ error: [
2739
+ "\u62C9\u53D6\u6700\u65B0\u57FA\u7EBF\u5931\u8D25\uFF0C\u5DF2\u64A4\u56DE\u672C\u6B21\u540C\u6B65\u64CD\u4F5C\uFF0C\u8BF7\u4EBA\u5DE5\u5904\u7406\u51B2\u7A81\u540E\u91CD\u8BD5\u9A8C\u6536\u5408\u5E76\u3002",
2740
+ ...failures.map((f) => `- ${f}`)
2741
+ ].join("\n")
2742
+ };
2743
+ }
2744
+ return { ok: true };
2745
+ }
2746
+
2747
+ // src/version.ts
2748
+ import { readFileSync as readFileSync8 } from "fs";
2749
+ import { dirname as dirname5, join as join8 } from "path";
2750
+ import { fileURLToPath as fileURLToPath2 } from "url";
2751
+ function readCliVersion() {
2752
+ try {
2753
+ const dir = dirname5(fileURLToPath2(import.meta.url));
2754
+ const pkgPath = join8(dir, "..", "package.json");
2755
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
2756
+ return pkg.version ?? "0.0.0";
2757
+ } catch {
2758
+ return "0.0.0";
2759
+ }
2760
+ }
2761
+
2762
+ // src/commands/update-skills.ts
2763
+ import { existsSync as existsSync7, mkdirSync as mkdirSync8, statSync as statSync4 } from "fs";
2764
+ import { join as join10 } from "path";
2765
+
2766
+ // src/skills-sync.ts
2767
+ import {
2768
+ copyFileSync as copyFileSync2,
2769
+ cpSync,
2770
+ existsSync as existsSync6,
2771
+ mkdirSync as mkdirSync7,
2772
+ readdirSync as readdirSync4,
2773
+ rmSync as rmSync2,
2774
+ statSync as statSync3,
2775
+ writeFileSync as writeFileSync9
2776
+ } from "fs";
2777
+ import { join as join9 } from "path";
2778
+ var AGENTS_TEMPLATE_PATH = join9(CLI_TEMPLATE_DIR, "AGENTS.md");
2779
+ var BASE_SKILLS_TEMPLATE_DIR = join9(CLI_TEMPLATE_DIR, "skills");
2780
+ var BASE_RULES_TEMPLATE_DIR = join9(CLI_TEMPLATE_DIR, "rules");
2781
+ function sanitizeSkillDirName(name) {
2782
+ const trimmed = name.trim();
2783
+ if (!trimmed) return "skill";
2784
+ return trimmed.replace(/[/\\:*?"<>|]/g, "_");
2785
+ }
2786
+ function listBaseSkillDirNames() {
2787
+ if (!existsSync6(BASE_SKILLS_TEMPLATE_DIR)) return [];
2788
+ return readdirSync4(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
2789
+ const path = join9(BASE_SKILLS_TEMPLATE_DIR, name);
2790
+ return statSync3(path).isDirectory();
2791
+ });
2792
+ }
2793
+ function syncAgentsGuide(apmDir) {
2794
+ if (!existsSync6(AGENTS_TEMPLATE_PATH)) return false;
2795
+ mkdirSync7(apmDir, { recursive: true });
2796
+ copyFileSync2(AGENTS_TEMPLATE_PATH, join9(apmDir, "AGENTS.md"));
2797
+ return true;
2798
+ }
2799
+ function listBaseRuleFileNames() {
2800
+ if (!existsSync6(BASE_RULES_TEMPLATE_DIR)) return [];
2801
+ return readdirSync4(BASE_RULES_TEMPLATE_DIR).filter((name) => {
2802
+ const path = join9(BASE_RULES_TEMPLATE_DIR, name);
2803
+ return statSync3(path).isFile();
2804
+ });
2805
+ }
2806
+ function syncBaseRules(rulesDir) {
2807
+ mkdirSync7(rulesDir, { recursive: true });
2808
+ const names = listBaseRuleFileNames();
2809
+ for (const name of names) {
2810
+ const src = join9(BASE_RULES_TEMPLATE_DIR, name);
2811
+ const dest = join9(rulesDir, name);
2812
+ copyFileSync2(src, dest);
2813
+ }
2814
+ return names;
2815
+ }
2816
+ function syncBaseSkills(skillsDir) {
2817
+ mkdirSync7(skillsDir, { recursive: true });
2818
+ const names = listBaseSkillDirNames();
2819
+ for (const name of names) {
2820
+ const src = join9(BASE_SKILLS_TEMPLATE_DIR, name);
2821
+ const dest = join9(skillsDir, name);
2822
+ cpSync(src, dest, { recursive: true, force: true });
2823
+ }
2824
+ return names;
2825
+ }
2826
+ function syncSupplementarySkills(skillsDir, list) {
2827
+ const baseNames = new Set(listBaseSkillDirNames());
2828
+ const apiDirNames = /* @__PURE__ */ new Set();
2829
+ const written = [];
2830
+ const skipped = [];
2831
+ for (const skill of list) {
2832
+ const dirName = sanitizeSkillDirName(skill.name);
2833
+ apiDirNames.add(dirName);
2834
+ if (baseNames.has(dirName)) {
2835
+ skipped.push(dirName);
2836
+ continue;
2837
+ }
2838
+ const skillDir = join9(skillsDir, dirName);
2839
+ mkdirSync7(skillDir, { recursive: true });
2840
+ writeFileSync9(join9(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
2841
+ written.push(dirName);
2842
+ }
2843
+ const removed = [];
2844
+ if (!existsSync6(skillsDir)) return { written, skipped, removed };
2845
+ for (const entry of readdirSync4(skillsDir)) {
2846
+ const full = join9(skillsDir, entry);
2847
+ if (!statSync3(full).isDirectory()) continue;
2848
+ if (baseNames.has(entry)) continue;
2849
+ if (apiDirNames.has(entry)) continue;
2850
+ rmSync2(full, { recursive: true, force: true });
2851
+ removed.push(entry);
2852
+ }
2853
+ return { written, skipped, removed };
2854
+ }
2855
+
2856
+ // src/commands/update-skills.ts
2857
+ async function syncWorkspaceSkills(cfg, workdir) {
2858
+ const apmDir = workspaceApmDir(workdir);
2859
+ const fsApmDir = toFsPath(apmDir);
2860
+ if (!existsSync7(fsApmDir)) {
2861
+ throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
2862
+ }
2863
+ const apmStat = statSync4(fsApmDir);
2864
+ if (!apmStat.isDirectory()) {
2865
+ throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
2866
+ }
2867
+ const api = createApmApiClient(cfg);
2868
+ const { list } = await api.cli.listSkills({});
2869
+ if (syncAgentsGuide(apmDir)) {
2870
+ console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
2871
+ }
2872
+ const rulesDir = join10(apmDir, "rules");
2873
+ const ruleNames = syncBaseRules(rulesDir);
2874
+ for (const name of ruleNames) {
2875
+ console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
2876
+ }
2877
+ const skillsDir = join10(apmDir, "skills");
2878
+ mkdirSync8(toFsPath(skillsDir), { recursive: true });
2879
+ const baseNames = syncBaseSkills(skillsDir);
2880
+ for (const name of baseNames) {
2881
+ console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u6280\u80FD: skills/${name}/`);
2882
+ }
2883
+ const { written, skipped, removed } = syncSupplementarySkills(
2884
+ skillsDir,
2885
+ list
2886
+ );
2887
+ for (const name of written) {
2888
+ console.log(`[apm] \u5DF2\u5199\u5165\u8865\u5145\u6280\u80FD: skills/${name}/SKILL.md`);
2889
+ }
2890
+ for (const name of skipped) {
2891
+ console.log(
2892
+ `[apm] \u5DF2\u8DF3\u8FC7\u4E0E\u57FA\u7840\u6280\u80FD\u540C\u540D\u7684\u8865\u5145\u6280\u80FD: ${name}\uFF08\u4FDD\u7559\u6A21\u677F\u7248\u672C\uFF09`
2893
+ );
2894
+ }
2895
+ for (const name of removed) {
2896
+ console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u8865\u5145\u6280\u80FD: skills/${name}/`);
2897
+ }
2898
+ console.log(
2899
+ `[apm] \u540C\u6B65\u5B8C\u6210\uFF1A${ruleNames.length} \u4E2A\u57FA\u7840\u89C4\u5219\uFF0C${baseNames.length} \u4E2A\u57FA\u7840\u6280\u80FD\uFF0C${written.length} \u4E2A\u8865\u5145\u6280\u80FD`
2900
+ );
2901
+ }
2902
+
2903
+ // src/commands/connect/cli-version-sync.ts
2904
+ import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
2905
+ import { join as join11 } from "path";
2906
+ var CLI_VERSION_FILE = ".cli-version.json";
2907
+ function manifestPath2(apmDir) {
2908
+ return join11(apmDir, CLI_VERSION_FILE);
2909
+ }
2910
+ function loadManifest(apmDir) {
2911
+ const path = toFsPath(manifestPath2(apmDir));
2912
+ if (!existsSync8(path)) {
2913
+ return null;
2914
+ }
2915
+ try {
2916
+ const parsed = JSON.parse(
2917
+ readFileSync9(path, "utf8")
2918
+ );
2919
+ if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
2920
+ return parsed;
2921
+ }
2922
+ } catch {
2923
+ }
2924
+ return null;
2925
+ }
2926
+ function saveManifest(apmDir, cliVersion) {
2927
+ const manifest = { version: 1, cliVersion };
2928
+ writeFileSync10(
2929
+ toFsPath(manifestPath2(apmDir)),
2930
+ `${JSON.stringify(manifest, null, 2)}
2931
+ `,
2932
+ "utf8"
2933
+ );
2934
+ }
2935
+ var syncedInSession = /* @__PURE__ */ new Map();
2936
+ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
2937
+ const cached = syncedInSession.get(workdir);
2938
+ if (cached === currentVersion) {
2939
+ return false;
2940
+ }
2941
+ const stored = loadManifest(workspaceApmDir(workdir));
2942
+ if (stored?.cliVersion === currentVersion) {
2943
+ syncedInSession.set(workdir, currentVersion);
2944
+ return false;
2945
+ }
2946
+ return true;
2947
+ }
2948
+ function markSkillsSyncedForCliVersion(workdir, cliVersion) {
2949
+ saveManifest(workspaceApmDir(workdir), cliVersion);
2950
+ syncedInSession.set(workdir, cliVersion);
2951
+ }
2952
+
2602
2953
  // src/commands/connect/handle-webide-message.ts
2954
+ var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
2955
+ "skip-plan",
2956
+ "start-develop",
2957
+ "fix-and-retest",
2958
+ "report-defect"
2959
+ ]);
2960
+ function shouldCommitAfterWebIdeMessage(action) {
2961
+ return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
2962
+ }
2603
2963
  async function updateStatus(cfg, messageId, status) {
2604
2964
  const api = createApmApiClient(cfg);
2605
2965
  await api.cli.webideUpdateMessageStatus({ id: messageId, status });
@@ -2627,6 +2987,15 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
2627
2987
  if (!didInit) {
2628
2988
  assertApmGitignoredInRepo(workdir);
2629
2989
  }
2990
+ const cliVersion = readCliVersion();
2991
+ if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
2992
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
2993
+ console.log(
2994
+ `[apm] CLI \u7248\u672C ${cliVersion} \u4E0E\u5DE5\u4F5C\u533A\u8BB0\u5F55\u4E0D\u4E00\u81F4\uFF0C\u6267\u884C update-skills`
2995
+ );
2996
+ await syncWorkspaceSkills(cfg, workdir);
2997
+ markSkillsSyncedForCliVersion(workdir, cliVersion);
2998
+ }
2630
2999
  resolveWorkspaceRepos(workdir);
2631
3000
  if (shouldRunBranch(branchCacheKey, workdir)) {
2632
3001
  if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
@@ -2640,9 +3009,30 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
2640
3009
  `[apm] step=branch skipped taskId=${taskId} workdir=${workdir}`
2641
3010
  );
2642
3011
  }
2643
- if (shouldEnsureWebIdeDraftPullRequests(msg.action)) {
3012
+ if (shouldEnsureWebIdePullRequests(msg.action)) {
2644
3013
  if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
2645
- await ensureWebIdeDraftPullRequests(cfg, taskId, workdir);
3014
+ const pushed = await pushWorkspaceRepos(workdir);
3015
+ console.log(
3016
+ `[apm] webide \u6D4B\u8BD5\u524D push action=${msg.action} repos=${pushed}`
3017
+ );
3018
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3019
+ await ensureWebIdePullRequests(cfg, taskId, workdir);
3020
+ }
3021
+ if (msg.action === "accept") {
3022
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3023
+ const sync = await syncWebIdeBaselineOrFail(workdir);
3024
+ if (!sync.ok) {
3025
+ await setError(cfg, messageId, sync.error);
3026
+ return;
3027
+ }
3028
+ try {
3029
+ await pushWorkspaceRepos(workdir);
3030
+ } catch (err) {
3031
+ console.warn(
3032
+ "[apm] webide accept \u540C\u6B65\u540E push \u5931\u8D25:",
3033
+ err instanceof Error ? err.message : err
3034
+ );
3035
+ }
2646
3036
  }
2647
3037
  const savedAgentId = loadWebIdeAgentId(workdir, taskId);
2648
3038
  const logSyncRef = { current: null };
@@ -2727,6 +3117,21 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
2727
3117
  );
2728
3118
  }
2729
3119
  }
3120
+ if (shouldCommitAfterWebIdeMessage(msg.action)) {
3121
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3122
+ const committed = await commitWorkspaceReposIfDirty(
3123
+ workdir,
3124
+ `chore(webide): sync after ${msg.action} (${taskId})`
3125
+ );
3126
+ console.log(
3127
+ `[apm] webide \u515C\u5E95 commit action=${msg.action} repos=${committed}`
3128
+ );
3129
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3130
+ const pushed = await pushWorkspaceRepos(workdir);
3131
+ console.log(
3132
+ `[apm] webide \u81EA\u52A8 push action=${msg.action} repos=${pushed}`
3133
+ );
3134
+ }
2730
3135
  await logSyncRef.current?.markRun(outcome.runId, "finished");
2731
3136
  await updateStatus(cfg, messageId, "SUCCESS");
2732
3137
  console.log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "7.1.10",
3
+ "version": "7.1.12",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -8,8 +8,8 @@
8
8
 
9
9
  1. 读取 `.apm/sessions/<会话ID>/session.yaml`
10
10
  2. 读取 `.apm/rules/reply.md`
11
- 3. **立即**用 `append_message` 工具回复(可先简短确认,再补充)
12
- 4. 按需阅读 `docs/` 下的文档,有进展继续调用 `append_message`
11
+ 3. **立即**用 `AppendMessage` 工具回复(可先简短确认,再补充)
12
+ 4. 按需阅读 `docs/` 下的文档,有进展继续调用 `AppendMessage`
13
13
 
14
14
  #### 重任务(开发、写方案、评审)
15
15
 
@@ -19,7 +19,7 @@
19
19
  4. 任务有阶段性进展或者任务完成后必须回复消息(具体见规则 `reply.md`)
20
20
  5. 写工作日志(具体见规则 `write_doc.md`)
21
21
 
22
- **禁止**在未回复前先读完所有 docs。有进展就先调用 `append_message`。
22
+ **禁止**在未回复前先读完所有 docs。有进展就先调用 `AppendMessage`。
23
23
 
24
24
  ### 目录指引
25
25
 
@@ -29,6 +29,9 @@
29
29
  - 规则: `.apm/rules/*`
30
30
  - reply.md:当需要回复消息时需要读取这个文档,记住回复规则
31
31
  - write_doc.md:当需要写文档时需要读取这个文档,记住写文档的规则
32
+ - webide_git_commit.md:WebIDE 开发/修码时读取,按规范分轮 commit,结束前 push
33
+ - webide_testcase.md:WebIDE 生成测试用例时读取,按规范编写并提交用例
34
+ - webide_merge.md:WebIDE 验收合并时读取;先解冲突再调 MergeWebIdePullRequests,需求相悖则拒绝合并
32
35
 
33
36
  - 仓库项目上下文(`.apm/project/`):
34
37
 
@@ -7,9 +7,9 @@
7
7
 
8
8
  ## 执行回复的方法
9
9
 
10
- 调用 `append_message` 工具,`content` 传入你要回复的消息内容。可多次调用补充进展。
10
+ 调用 `AppendMessage` 工具,`content` 传入你要回复的消息内容。可多次调用补充进展。
11
11
 
12
- 示例: `append_message(content="收到,正在分析后端改动范围。")`
12
+ 示例: `AppendMessage(content="收到,正在分析后端改动范围。")`
13
13
 
14
14
  ## 写文档的方法
15
15
 
@@ -0,0 +1,16 @@
1
+ ## WebIDE Git 提交规范
2
+
3
+ 适用场景:WebIDE 开发阶段(`skip-plan` / `start-develop`)及修码复测(`fix-and-retest`)中涉及代码改动时。
4
+
5
+ ### 分轮提交
6
+
7
+ 1. 按需求点分轮实现:每完成一个可独立验收的需求点,立即 `git add` 并 commit,不要把多轮改动攒成一次提交。
8
+ 2. commit message 用简短中文说明本轮改动(例:`feat: 实现登录表单校验`)。
9
+ 3. 多仓工作区时,只在实际改动过的仓库提交。
10
+
11
+ ### 结束前检查
12
+
13
+ 本轮全部工作结束前:
14
+
15
+ 1. 确认工作区干净(`git status` 无未提交变更);若仍有残留改动,补一次 commit。
16
+ 2. 将当前分支推送到远程:已有 upstream 用 `git push`,否则 `git push -u origin HEAD`。多仓工作区时,只 push 实际有新 commit 的仓库。
@@ -0,0 +1,39 @@
1
+ ## WebIDE 合并规范
2
+
3
+ 适用场景:WebIDE 验收通过(`accept`)后:同步最新基线 → 对照原始任务审核 → 合并本任务 PR。
4
+
5
+ ### 目标
6
+
7
+ 将本任务各仓库 `feat/task-<taskId>` 分支上的变更安全合并进基线分支(通常为 `main`)。
8
+
9
+ ### 步骤
10
+
11
+ 1. **先拉取最新基线并处理冲突,再审核,最后调用合并工具**
12
+
13
+ - 在各相关仓库拉取最新基线(`git fetch`),将基线 merge/rebase 进特性分支。
14
+ - 有冲突时:在本地解冲突后 commit 并 `git push`(必要时 `--force-with-lease` 仅用于已 rebase 的特性分支)。
15
+ - 解冲突原则:保留本任务需求与已确认计划所要求的行为;吸收基线中合理且不冲突的改动。
16
+ - **解冲突失败或无法安全取舍**:立即 `git merge --abort` / `git rebase --abort` 撤回本次同步,保持特性分支为同步前状态;**禁止**调用合并工具。
17
+
18
+ 2. **不可调和冲突 / 同步失败 → 拒绝合并**
19
+
20
+ - 若冲突双方改动在需求/业务逻辑上相悖,或本地同步已 abort:
21
+ - **禁止**调用 `MergeWebIdePullRequests`
22
+ - 用 `AppendMessage` 说明冲突文件、矛盾点、为何无法自动取舍,以及已执行的回退
23
+ - 以错误结束本轮(抛错或明确失败),等待人工介入(前端将展示失败信息)
24
+
25
+ 3. **对照原始任务审核代码**
26
+
27
+ - 在合并前快速核对:改动是否覆盖任务需求与已确认计划;是否引入明显与需求相悖的行为。
28
+ - 若审核不通过:同样禁止合并,AppendMessage 说明问题并以错误结束。
29
+
30
+ 4. **确认可安全合并后**
31
+ - 调用一次 `MergeWebIdePullRequests`(无参数),由平台合并本任务全部 OPEN PR
32
+ - 再用 `AppendMessage` 汇总合并结果(PR 编号、仓库)
33
+
34
+ ### 禁止
35
+
36
+ - 未拉取最新基线 / 未检查冲突就直接调合并工具
37
+ - 同步冲突未 abort 就继续合并
38
+ - 为强行合并而删改与需求相悖的关键逻辑却不说明
39
+ - 在仍有草稿 PR / 明显冲突未解决时假装成功
@@ -0,0 +1,47 @@
1
+ ## WebIDE 测试用例编写规范
2
+
3
+ 适用场景:WebIDE `generate-cases` 阶段,根据任务需求与本地代码改动生成自动化测试用例。
4
+
5
+ ### 产出方式
6
+
7
+ 1. 仅在当前工作区 cwd 内阅读/搜索代码,不要访问工作区外路径。
8
+ 2. 用中文经 `AppendMessage` 简要说明用例覆盖范围。
9
+ 3. **必须**调用 `UpsertWebIdeTestCases`,一次提交完整用例列表;工具会写入平台并进入待测阶段。
10
+ 4. 不要只在对话里列出用例而不调用工具。
11
+
12
+ ### 用例字段
13
+
14
+ 每条用例包含:
15
+
16
+ | 字段 | 要求 |
17
+ | ------------- | ---------------------------------------------------------------- |
18
+ | `name` | 简短稳定的用例标识,可用中文或英文短句,避免空泛(如「测试 1」) |
19
+ | `description` | 说明本用例验证什么(中文),一两句即可 |
20
+ | `steps` | 有序、可执行的步骤数组;每步一句,写清操作与预期结果 |
21
+ | `sortOrder` | 可选,展示顺序(从 0 起) |
22
+
23
+ ### 编写原则
24
+
25
+ 1. **对齐需求与改动**:覆盖本任务实现的核心路径;对照本地 diff / 关键代码,不要凭空编造未实现的功能。
26
+ 2. **主路径优先**:至少覆盖成功主流程;再按风险补边界、校验失败、权限拒绝等必要负面场景。
27
+ 3. **步骤可执行**:步骤应能被人或自动化按字面执行(打开哪页、点哪个按钮、输入什么、看到什么),禁止「验证功能正常」这类空话。
28
+ 4. **一条一焦点**:每条用例聚焦一个可验收点,不要把无关场景塞进同一步骤列表。
29
+ 5. **适量即可**:宁缺毋滥;通常数条到十余条足够,避免大量重复或琐碎用例。
30
+
31
+ ### 调用示例
32
+
33
+ ```text
34
+ UpsertWebIdeTestCases({
35
+ cases: [
36
+ {
37
+ name: "登录成功进入首页",
38
+ description: "使用合法账号密码登录后应进入系统首页",
39
+ steps: [
40
+ "打开登录页",
41
+ "输入正确的用户名与密码,点击登录",
42
+ "应跳转到首页,并显示当前用户名"
43
+ ]
44
+ }
45
+ ]
46
+ })
47
+ ```
@@ -77,4 +77,4 @@
77
77
 
78
78
  **注意:不做联调。** 前后端各自按 `API.md` 交付,接口对不上属于契约或实现问题,由 diff 评审与人工验收暴露后打回修复;禁止自行发起「联调」「接口实测」类的开放式动作。
79
79
 
80
- 完成后用 `append_message` 回复:改动概述 + 白名单对账 + 构建结果,并 `@` 评审角色进行 diff 评审。
80
+ 完成后用 `AppendMessage` 回复:改动概述 + 白名单对账 + 构建结果,并 `@` 评审角色进行 diff 评审。
@@ -44,13 +44,13 @@ apm create-pr --session <会话ID> --title "<需求摘要>" --content "<计划
44
44
 
45
45
  ### 步骤 4:输出结论
46
46
 
47
- **通过**:用 `append_message` 回复评审结论,并 `@项目经理` 交人验收,回复中必须包含:
47
+ **通过**:用 `AppendMessage` 回复评审结论,并 `@项目经理` 交人验收,回复中必须包含:
48
48
 
49
49
  1. 「diff 评审通过」+ 一句话改动概述;
50
50
  2. **PR 链接**(上一步 `apm create-pr` 输出;创建失败则注明原因);
51
51
  3. 提示按 `CHECKLIST.md` 逐条验收(部署与回归验收由人工或平台完成)。
52
52
 
53
- **不通过**:用 `append_message` 输出问题清单(每条注明文件 + 问题 + 依据哪条计划/需求),`@` 对应工程师打回修改。**禁止自己动手改。**
53
+ **不通过**:用 `AppendMessage` 输出问题清单(每条注明文件 + 问题 + 依据哪条计划/需求),`@` 对应工程师打回修改。**禁止自己动手改。**
54
54
 
55
55
  ---
56
56
 
@@ -14,7 +14,7 @@
14
14
  - 影响实现范围时 → 修订「实现步骤 / 改动文件白名单」;后端涉及接口则同步 `docs/API.md`。
15
15
  5. 重写 PLAN §1「需求理解」(融合 PM 确认口径)。
16
16
  6. **`apm sync-document`** 同步 PLAN(及 API 文档)。
17
- 7. **`append_message`**:本端需求理解摘要 + @项目经理,请其回复「理解正确,可进入开发」。
17
+ 7. **`AppendMessage`**:本端需求理解摘要 + @项目经理,请其回复「理解正确,可进入开发」。
18
18
  8. **Read/Update** `TODO.md`:勾选相关待办。
19
19
 
20
20
  **禁止** `apm assumptions --mark-backfilled`(已废弃)。
@@ -19,7 +19,7 @@
19
19
 
20
20
  逐条对照需求原文与计划:
21
21
 
22
- - **计划与需求矛盾、或需求关键点在计划中没有覆盖**:不要写清单,先用 `append_message` 指出问题并 `@` 对应工程师打回,等计划修订后再写。
22
+ - **计划与需求矛盾、或需求关键点在计划中没有覆盖**:不要写清单,先用 `AppendMessage` 指出问题并 `@` 对应工程师打回,等计划修订后再写。
23
23
  - **开发门禁未通过**(`apm dev-gate` 返回 `readyForDev: false`):**不要写清单**,先提醒 PM / 执行 `apm-update-plan`。
24
24
  - 计划覆盖完整:进入步骤 3。
25
25
 
@@ -18,7 +18,7 @@
18
18
 
19
19
  **Read** `.apm/sessions/<会话ID>/TASK.md`。
20
20
 
21
- - **不涉及本端改动**:`append_message` 回复理由后结束。
21
+ - **不涉及本端改动**:`AppendMessage` 回复理由后结束。
22
22
  - **涉及本端改动**:进入步骤 2。
23
23
 
24
24
  ### 步骤 2:有限调研
@@ -36,7 +36,7 @@
36
36
 
37
37
  1. `apm sync-document --file FRONTEND-PLAN.md`(或 BACKEND)。
38
38
  2. **有假设且 ASSUMPTIONS 已成功 sync**:@项目经理 请其在 Web **「待您确认」** Panel 点选;说明「以上假设确认前不开始开发」。
39
- 3. **无假设**:`append_message` 发送「本端需求理解」摘要并 @项目经理 请确认。
39
+ 3. **无假设**:`AppendMessage` 发送「本端需求理解」摘要并 @项目经理 请确认。
40
40
 
41
41
  > **禁止**在 ASSUMPTIONS sync 未成功时 @ 项目经理确认假设;PLAN 中「待确认 N 项」由服务端 sync ASSUMPTIONS 后自动更新,AI 不要手写具体数字。
42
42