ai-project-manage-cli 6.0.42 → 6.0.44-alpha.1

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
@@ -127,6 +127,13 @@ function resolveWorkdirPath(cwd = process.cwd()) {
127
127
  return normalizeWorkdirPath(absolute);
128
128
  }
129
129
  }
130
+ function requireRemoteWorkdir(workdir) {
131
+ const trimmed = typeof workdir === "string" ? workdir.trim() : "";
132
+ if (!trimmed) {
133
+ throw new Error("[apm] \u8FDC\u7A0B\u6D88\u606F\u7F3A\u5C11\u5DE5\u4F5C\u76EE\u5F55 workdir");
134
+ }
135
+ return resolveWorkdirPath(trimmed);
136
+ }
130
137
 
131
138
  // src/command-utils.ts
132
139
  var __dirname = dirname(fileURLToPath(import.meta.url));
@@ -611,7 +618,7 @@ async function runBranch(sessionId, options = {}) {
611
618
 
612
619
  // src/commands/pull.ts
613
620
  import { writeFileSync as writeFileSync6 } from "fs";
614
- import { dirname as dirname2, join as join7 } from "path";
621
+ import { join as join7 } from "path";
615
622
  import { stringify as yamlStringify } from "yaml";
616
623
 
617
624
  // src/session-messages-xml.ts
@@ -908,13 +915,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
908
915
  }
909
916
 
910
917
  // src/commands/pull.ts
911
- function resolvePullWorkdir(apmRoot) {
912
- if (apmRoot) {
913
- return resolveWorkdirPath(dirname2(apmRoot));
914
- }
915
- return resolveWorkdirPath();
916
- }
917
- async function runPull(sessionId, apmRoot) {
918
+ async function runPull(sessionId, remoteWorkdir) {
918
919
  const trimmedId = sessionId.trim();
919
920
  if (!trimmedId) {
920
921
  console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
@@ -922,6 +923,8 @@ async function runPull(sessionId, apmRoot) {
922
923
  }
923
924
  const cfg = await ensureLoggedConfig();
924
925
  const api = createApmApiClient(cfg);
926
+ const workdir = remoteWorkdir === void 0 ? resolveWorkdirPath() : requireRemoteWorkdir(remoteWorkdir);
927
+ const apmRoot = workspaceApmDir(workdir);
925
928
  const [detail, members, documents, attachments, messages] = await Promise.all(
926
929
  [
927
930
  api.cli.sessionDetail({ sessionId: trimmedId }),
@@ -976,8 +979,7 @@ async function runPull(sessionId, apmRoot) {
976
979
  "utf8"
977
980
  );
978
981
  await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
979
- const workdirPath = resolvePullWorkdir(apmRoot);
980
- await syncPlatformRules(cfg, trimmedId, workdirPath, apmRoot);
982
+ await syncPlatformRules(cfg, trimmedId, workdir, apmRoot);
981
983
  console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
982
984
  return dir;
983
985
  }
@@ -987,12 +989,12 @@ import { spawnSync } from "child_process";
987
989
 
988
990
  // src/version.ts
989
991
  import { readFileSync as readFileSync5 } from "fs";
990
- import { dirname as dirname3, join as join8 } from "path";
992
+ import { dirname as dirname2, join as join8 } from "path";
991
993
  import { fileURLToPath as fileURLToPath2 } from "url";
992
994
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
993
995
  function readCliVersion() {
994
996
  try {
995
- const dir = dirname3(fileURLToPath2(import.meta.url));
997
+ const dir = dirname2(fileURLToPath2(import.meta.url));
996
998
  const pkgPath = join8(dir, "..", "package.json");
997
999
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
998
1000
  return pkg.version ?? "0.0.0";
@@ -1430,7 +1432,6 @@ import {
1430
1432
  CursorAgentError
1431
1433
  } from "@cursor/sdk";
1432
1434
  import { setMaxListeners as setMaxListeners2 } from "node:events";
1433
- import { resolve as resolve4 } from "path";
1434
1435
 
1435
1436
  // src/session-utils.ts
1436
1437
  var EventSession = class {
@@ -1592,7 +1593,7 @@ ${JSON.stringify(event, null, 2)}
1592
1593
 
1593
1594
  // src/commands/connect/agent-session-registry.ts
1594
1595
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
1595
- import { dirname as dirname4, resolve as resolve3 } from "node:path";
1596
+ import { dirname as dirname3, resolve as resolve3 } from "node:path";
1596
1597
  function registryPath(workdir, sessionId) {
1597
1598
  return resolve3(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
1598
1599
  }
@@ -1618,7 +1619,7 @@ function readRegistry(path10) {
1618
1619
  return {};
1619
1620
  }
1620
1621
  function writeRegistry(path10, registry) {
1621
- mkdirSync5(dirname4(path10), { recursive: true });
1622
+ mkdirSync5(dirname3(path10), { recursive: true });
1622
1623
  writeFileSync7(path10, `${JSON.stringify(registry, null, 2)}
1623
1624
  `, "utf8");
1624
1625
  }
@@ -1755,6 +1756,16 @@ function createAppendMessageCustomTools(cfg, messageId) {
1755
1756
  };
1756
1757
  }
1757
1758
 
1759
+ // src/commands/connect/playwright-mcp.ts
1760
+ function createPlaywrightMcpServers() {
1761
+ return {
1762
+ playwright: {
1763
+ command: "npx",
1764
+ args: ["@playwright/mcp@latest", "--browser", "chrome", "--vision"]
1765
+ }
1766
+ };
1767
+ }
1768
+
1758
1769
  // src/commands/connect/cursor-agent.ts
1759
1770
  setMaxListeners2(50);
1760
1771
  installAbortSignalDebug();
@@ -1782,7 +1793,8 @@ async function obtainAgent(ctx) {
1782
1793
  model: { id: ctx.model || "default" },
1783
1794
  local: {
1784
1795
  cwd: ctx.cwd
1785
- }
1796
+ },
1797
+ mcpServers: createPlaywrightMcpServers()
1786
1798
  };
1787
1799
  const savedAgentId = ctx.user ? loadSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user) : void 0;
1788
1800
  if (savedAgentId) {
@@ -1816,16 +1828,16 @@ async function runCursorAgent(cfg, ctx, options) {
1816
1828
  if (!apiKey) {
1817
1829
  throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
1818
1830
  }
1819
- const cwd = resolve4(ctx.workdir);
1831
+ const workdir = resolveWorkdirPath(ctx.workdir);
1820
1832
  const prompt = ctx.prompt;
1821
1833
  console.log(
1822
- `[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${cwd}`
1834
+ `[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${workdir}`
1823
1835
  );
1824
1836
  const { agent, resumed } = await obtainAgent({
1825
1837
  apiKey,
1826
1838
  model: ctx.model,
1827
- cwd,
1828
- workdir: ctx.workdir,
1839
+ cwd: workdir,
1840
+ workdir,
1829
1841
  sessionId: ctx.sessionId,
1830
1842
  user: ctx.user
1831
1843
  });
@@ -1850,6 +1862,7 @@ async function runCursorAgent(cfg, ctx, options) {
1850
1862
  logAbortSignalStats(signal, "runCursorAgent:after-addListener");
1851
1863
  try {
1852
1864
  const run = await agent.send(prompt, {
1865
+ mcpServers: createPlaywrightMcpServers(),
1853
1866
  local: {
1854
1867
  customTools: createAppendMessageCustomTools(cfg, ctx.messageId)
1855
1868
  }
@@ -1884,7 +1897,7 @@ async function runCursorAgent(cfg, ctx, options) {
1884
1897
  });
1885
1898
  console.error(`[apm] ${failureMessage}`);
1886
1899
  if (resumed) {
1887
- clearSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user);
1900
+ clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
1888
1901
  }
1889
1902
  throw new Error(failureMessage);
1890
1903
  }
@@ -1895,7 +1908,7 @@ async function runCursorAgent(cfg, ctx, options) {
1895
1908
  } catch (err) {
1896
1909
  if (err instanceof CursorAgentError) {
1897
1910
  if (resumed) {
1898
- clearSessionAgentId(ctx.workdir, ctx.sessionId, ctx.user);
1911
+ clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
1899
1912
  }
1900
1913
  throw new Error(
1901
1914
  `Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
@@ -1912,23 +1925,27 @@ async function runCursorAgent(cfg, ctx, options) {
1912
1925
 
1913
1926
  // src/commands/connect/pre-step-cache.ts
1914
1927
  var PULL_TTL_MS = 3e4;
1915
- var lastBranchSessionId = null;
1916
- var lastPullAtBySession = /* @__PURE__ */ new Map();
1917
- function shouldRunBranch(sessionId) {
1918
- return lastBranchSessionId !== sessionId;
1928
+ function sessionWorkdirKey(sessionId, workdir) {
1929
+ return `${sessionId}\0${workdir}`;
1930
+ }
1931
+ var lastBranchKey = null;
1932
+ var lastPullAtByKey = /* @__PURE__ */ new Map();
1933
+ function shouldRunBranch(sessionId, workdir) {
1934
+ return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
1919
1935
  }
1920
- function markBranchDone(sessionId) {
1921
- lastBranchSessionId = sessionId;
1936
+ function markBranchDone(sessionId, workdir) {
1937
+ lastBranchKey = sessionWorkdirKey(sessionId, workdir);
1922
1938
  }
1923
- function shouldRunPull(sessionId) {
1924
- const last = lastPullAtBySession.get(sessionId);
1939
+ function shouldRunPull(sessionId, workdir) {
1940
+ const key = sessionWorkdirKey(sessionId, workdir);
1941
+ const last = lastPullAtByKey.get(key);
1925
1942
  if (last == null) {
1926
1943
  return true;
1927
1944
  }
1928
1945
  return Date.now() - last >= PULL_TTL_MS;
1929
1946
  }
1930
- function markPullDone(sessionId) {
1931
- lastPullAtBySession.set(sessionId, Date.now());
1947
+ function markPullDone(sessionId, workdir) {
1948
+ lastPullAtByKey.set(sessionWorkdirKey(sessionId, workdir), Date.now());
1932
1949
  }
1933
1950
 
1934
1951
  // src/commands/connect/run-slot-pool.ts
@@ -1941,10 +1958,10 @@ function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT) {
1941
1958
  active += 1;
1942
1959
  return Promise.resolve();
1943
1960
  }
1944
- return new Promise((resolve6) => {
1961
+ return new Promise((resolve5) => {
1945
1962
  waiters.push(() => {
1946
1963
  active += 1;
1947
- resolve6();
1964
+ resolve5();
1948
1965
  });
1949
1966
  });
1950
1967
  };
@@ -1978,6 +1995,8 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
1978
1995
  if (isUserCancelled(ctx)) return;
1979
1996
  if (signal.aborted) return;
1980
1997
  const messageId = msg.messageId;
1998
+ const workdir = requireRemoteWorkdir(msg.workdir);
1999
+ const apmRoot = workspaceApmDir(workdir);
1981
2000
  const runStep = async (step, fn) => {
1982
2001
  const startedAt = Date.now();
1983
2002
  try {
@@ -1995,24 +2014,18 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
1995
2014
  "status-typing",
1996
2015
  () => updateMessageStatus(cfg, messageId, "TYPING")
1997
2016
  );
1998
- if (shouldRunBranch(msg.sessionId)) {
2017
+ if (shouldRunBranch(msg.sessionId, workdir)) {
1999
2018
  if (signal.aborted) return;
2000
- await runStep(
2001
- "branch",
2002
- () => runBranch(msg.sessionId, { cwd: msg.workdir })
2003
- );
2004
- markBranchDone(msg.sessionId);
2019
+ await runStep("branch", () => runBranch(msg.sessionId, { cwd: workdir }));
2020
+ markBranchDone(msg.sessionId, workdir);
2005
2021
  } else {
2006
2022
  console.log(`[apm] step=branch skipped sessionId=${msg.sessionId}`);
2007
2023
  }
2008
2024
  let pullRan = false;
2009
- if (shouldRunPull(msg.sessionId)) {
2025
+ if (shouldRunPull(msg.sessionId, workdir)) {
2010
2026
  if (signal.aborted) return;
2011
- await runStep(
2012
- "pull",
2013
- () => runPull(msg.sessionId, workspaceApmDir(msg.workdir))
2014
- );
2015
- markPullDone(msg.sessionId);
2027
+ await runStep("pull", () => runPull(msg.sessionId, workdir));
2028
+ markPullDone(msg.sessionId, workdir);
2016
2029
  pullRan = true;
2017
2030
  } else {
2018
2031
  console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
@@ -2021,7 +2034,7 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
2021
2034
  if (signal.aborted) return;
2022
2035
  await runStep(
2023
2036
  "commit-pull",
2024
- () => commitWorkingTreeIfDirty(msg.workdir, "fix: apm pull")
2037
+ () => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
2025
2038
  );
2026
2039
  } else {
2027
2040
  console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
@@ -2037,7 +2050,7 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
2037
2050
  prompt: msg.content,
2038
2051
  model: msg.model,
2039
2052
  apiKey: msg.apiKey,
2040
- workdir: msg.workdir,
2053
+ workdir,
2041
2054
  user: msg.user
2042
2055
  },
2043
2056
  { signal }
@@ -2045,11 +2058,11 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
2045
2058
  );
2046
2059
  await runStep(
2047
2060
  "sync-documents",
2048
- () => syncSessionDocuments(cfg, msg.sessionId, workspaceApmDir(msg.workdir))
2061
+ () => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
2049
2062
  );
2050
2063
  await runStep(
2051
2064
  "commit-files",
2052
- () => commitWorkingTreeIfDirty(msg.workdir, "chore(apm): commit working tree")
2065
+ () => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
2053
2066
  );
2054
2067
  await runStep(
2055
2068
  "status-success",
@@ -2109,7 +2122,7 @@ async function runConnect(options) {
2109
2122
  }
2110
2123
  const url = buildAgentWsUrl(cfg.baseUrl, resolveApiKey(cfg));
2111
2124
  console.log(`[apm] \u8FDE\u63A5 ${cfg.baseUrl} \u2026`);
2112
- await new Promise((resolve6, reject) => {
2125
+ await new Promise((resolve5, reject) => {
2113
2126
  const ws = new WebSocket(url);
2114
2127
  let stopHeartbeat;
2115
2128
  let shuttingDown = false;
@@ -2138,7 +2151,7 @@ async function runConnect(options) {
2138
2151
  ]);
2139
2152
  } catch {
2140
2153
  }
2141
- resolve6();
2154
+ resolve5();
2142
2155
  process.exit(code);
2143
2156
  };
2144
2157
  ws.on("open", () => {
@@ -2226,11 +2239,11 @@ import path5 from "node:path";
2226
2239
 
2227
2240
  // src/commands/deploy/internal/apm-config.ts
2228
2241
  import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
2229
- import { resolve as resolve5 } from "node:path";
2242
+ import { resolve as resolve4 } from "node:path";
2230
2243
  function loadApmConfig(options) {
2231
- const p = resolve5(
2244
+ const p = resolve4(
2232
2245
  process.cwd(),
2233
- options?.configPath ?? resolve5(workspaceApmDir(), "apm.config.json")
2246
+ options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
2234
2247
  );
2235
2248
  if (!existsSync9(p)) {
2236
2249
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
@@ -2497,17 +2510,17 @@ var DockerodeClient = class {
2497
2510
  await this.client.getImage(image).remove({ force: true });
2498
2511
  }
2499
2512
  async pullImage(image, auth) {
2500
- const stream = await new Promise((resolve6, reject) => {
2513
+ const stream = await new Promise((resolve5, reject) => {
2501
2514
  const pullOptions = auth ? { authconfig: auth } : void 0;
2502
2515
  this.client.pull(image, pullOptions, (err, output) => {
2503
2516
  if (err || !output) {
2504
2517
  reject(err ?? new Error("docker pull \u8FD4\u56DE\u7A7A\u8F93\u51FA"));
2505
2518
  return;
2506
2519
  }
2507
- resolve6(output);
2520
+ resolve5(output);
2508
2521
  });
2509
2522
  });
2510
- await new Promise((resolve6, reject) => {
2523
+ await new Promise((resolve5, reject) => {
2511
2524
  this.client.modem.followProgress(
2512
2525
  stream,
2513
2526
  (err) => {
@@ -2515,7 +2528,7 @@ var DockerodeClient = class {
2515
2528
  reject(err);
2516
2529
  return;
2517
2530
  }
2518
- resolve6();
2531
+ resolve5();
2519
2532
  },
2520
2533
  () => void 0
2521
2534
  );
@@ -3006,14 +3019,14 @@ var MinioClient = class {
3006
3019
  async deleteObjectsByPrefix(bucket, prefix) {
3007
3020
  const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
3008
3021
  const keys = [];
3009
- await new Promise((resolve6, reject) => {
3022
+ await new Promise((resolve5, reject) => {
3010
3023
  objectsStream.on("data", (obj) => {
3011
3024
  if (obj.name) {
3012
3025
  keys.push(obj.name);
3013
3026
  }
3014
3027
  });
3015
3028
  objectsStream.on("error", reject);
3016
- objectsStream.on("end", resolve6);
3029
+ objectsStream.on("end", resolve5);
3017
3030
  });
3018
3031
  const chunkSize = 500;
3019
3032
  for (let i = 0; i < keys.length; i += chunkSize) {
@@ -3251,7 +3264,7 @@ async function ensureRemoteDir(sftp, dir) {
3251
3264
  }
3252
3265
  }
3253
3266
  function execCommand(client, command) {
3254
- return new Promise((resolve6, reject) => {
3267
+ return new Promise((resolve5, reject) => {
3255
3268
  client.exec(command, (err, stream) => {
3256
3269
  if (err) return reject(err);
3257
3270
  let stdout = "";
@@ -3261,7 +3274,7 @@ function execCommand(client, command) {
3261
3274
  reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
3262
3275
  return;
3263
3276
  }
3264
- resolve6(stdout);
3277
+ resolve5(stdout);
3265
3278
  }).on("data", (data) => {
3266
3279
  stdout += data.toString();
3267
3280
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.42",
3
+ "version": "6.0.44-alpha.1",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -6,7 +6,11 @@
6
6
 
7
7
  1. 用 **Read** 工具阅读本端计划:前端读 `.apm/sessions/<会话ID>/docs/FRONTEND-PLAN.md`,后端读 `docs/BACKEND-PLAN.md`;计划不存在则退出流程并回复说明(兼容旧流程:若存在 `PRD.md` + `FRONTEND.md` / `BACKEND.md` + `API.md`,按旧文档执行)。
8
8
  2. 前端涉及接口对接时,以后端计划中的「API 契约」章节为准,**不等后端部署完成**;契约没写清的字段 `@后端` 确认,禁止自行猜测。
9
- 3. 计划「假设」章节存在未确认项时,先 `@项目经理` 确认,确认前不开始开发。
9
+ 3. **假设门禁(开发前必须检查)**:查看计划「依据与假设」章节——
10
+ - 「假设」仍有未确认项:**Read** `.apm/sessions/<会话ID>/messages.xml`,查找项目经理是否已回复确认;
11
+ - 项目经理已回复:先按 `.apm/skills/apm-write-plan/SKILL.md` 步骤 5 把确认结果**回填进计划文档并同步**(确认的假设移入「依据」,否定的修订实现步骤与白名单),然后再开发;
12
+ - 项目经理未回复:`@项目经理` 列出待确认假设,**停止本次开发**。
13
+ 严禁带着未回填的澄清直接开发——项目经理在聊天里给过的口径若没落进计划,开发与 diff 评审都不会认。
10
14
 
11
15
  ### 步骤 2: 明确开发模式
12
16
 
@@ -44,6 +44,20 @@
44
44
  - **无假设**:回复计划已就绪,可进入测试要点编写。
45
45
  - 前端计划依赖的接口后端契约还没出:在计划「期望接口」小节写出前端期望的接口形态,回复时 `@后端` 对齐,不要空等。
46
46
 
47
+ ### 步骤 5:假设回填(项目经理确认后必须执行)
48
+
49
+ 当你被安排「根据项目经理的确认更新计划」,或发现项目经理已回复假设确认时:
50
+
51
+ 1. **Read** `.apm/sessions/<会话ID>/messages.xml`,找到项目经理针对假设的最新回复,**逐条对照你列出的假设**,不要只看 speakContext 的转述。
52
+ 2. 更新计划文档:
53
+ - 被确认的假设 → **移入「依据」表格**,来源写「项目经理确认(第 N 轮)」;
54
+ - 被否定或修正的假设 → 按项目经理给出的口径**修订「实现步骤」与「改动文件白名单」**;
55
+ - 项目经理没有回应的假设 → 保留在「假设」中,再次 `@项目经理` 追问。
56
+ 3. 重新执行 `apm sync-document` 同步计划。
57
+ 4. 回复消息:逐条说明每个假设的处理结果(确认采纳 / 按口径修订了什么),全部解决则声明「假设已清零,可进入测试要点编写」。
58
+
59
+ **禁止**跳过回填直接开发:澄清结论必须落进计划文档,后续开发与 diff 评审都只认计划文档,不认聊天记录。
60
+
47
61
  ---
48
62
 
49
63
  ## 写作要求