ai-project-manage-cli 6.0.94 → 6.0.96

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
@@ -55,7 +55,7 @@ apm daemon start
55
55
  apm daemon start -f
56
56
  ```
57
57
 
58
- PM2 已内置;同一时刻仅允许一个 connect。启动时会自动检测 CLI 更新并刷新守护配置。
58
+ 使用全局 PM2(未安装时会自动执行 `npm install -g pm2`);同一时刻仅允许一个 connect。启动时会自动检测 CLI 更新并刷新守护配置。
59
59
 
60
60
  `apm pull` 会自动将平台登记的**仓库项目文档**同步到 `.apm/project/`(含 `manifest.json`)。Agent 修改 `.apm/project/` 下文件后,`apm connect` 处理完消息会自动推回平台。
61
61
 
package/dist/index.js CHANGED
@@ -2029,9 +2029,169 @@ async function runSyncProjectDocuments(options) {
2029
2029
  }
2030
2030
 
2031
2031
  // src/commands/sync-document.ts
2032
- import { existsSync as existsSync10 } from "fs";
2032
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
2033
2033
  import { basename as basename3 } from "path";
2034
2034
 
2035
+ // src/assumptions/local-validate.ts
2036
+ var NO_ASSUMPTIONS_RE = /无[,,]?\s*口径均有依据/;
2037
+ var BLOCK_HEADER_RE = /^####\s+(A\d+)\s*$/im;
2038
+ var DEV_PATTERNS = [
2039
+ { pattern: /status\s*=/i, message: "\u5305\u542B status= \u7B49\u6280\u672F\u679A\u4E3E" },
2040
+ { pattern: /\.(java|vue|ts|tsx|jsx|sql)\b/i, message: "\u5305\u542B\u6587\u4EF6\u8DEF\u5F84" },
2041
+ { pattern: /`[^`]+`/i, message: "\u5305\u542B\u4EE3\u7801\u53CD\u5F15\u53F7" },
2042
+ { pattern: /\bSELECT\b|\bINSERT\b|\bALTER\b/i, message: "\u5305\u542B SQL \u5173\u952E\u5B57" },
2043
+ { pattern: /\w+\.\w+/i, message: "\u5305\u542B\u7C7B\u540D\u6216\u8868\u5B57\u6BB5\u70B9\u53F7\u5199\u6CD5" },
2044
+ { pattern: /\b[a-z]+_[a-z0-9_]+\b/i, message: "\u5305\u542B snake_case \u5B57\u6BB5\u540D" }
2045
+ ];
2046
+ var BUSINESS_VERBS = /提交|展示|校验|驳回|审批|允许|禁止|筛选|列表|详情|暂存|删除|新增|修改|导出|导入/;
2047
+ function parseOptionsBlock(text) {
2048
+ const options = [];
2049
+ for (const line of text.split("\n")) {
2050
+ const trimmed = line.trim();
2051
+ const match = trimmed.match(/^-\s*([a-zA-Z0-9_]+)[::]\s*(.+)$/);
2052
+ if (match) {
2053
+ options.push({ id: match[1], label: match[2].trim() });
2054
+ }
2055
+ }
2056
+ return options;
2057
+ }
2058
+ function parseField(block, label) {
2059
+ const re = new RegExp(
2060
+ `-\\s*\\*\\*${label}\\*\\*(?:\\\uFF08[^\uFF09]*\\\uFF09)?[:\uFF1A]\\s*(.*)$`,
2061
+ "im"
2062
+ );
2063
+ const match = block.match(re);
2064
+ const value = match?.[1]?.trim();
2065
+ return value || void 0;
2066
+ }
2067
+ function parseStructuredBlock(code, block) {
2068
+ const pmQuestion = parseField(block, "\u95EE") ?? parseField(block, "prompt");
2069
+ if (!pmQuestion) return null;
2070
+ const optionsSection = block.match(
2071
+ /-\s*\*\*选项\*\*[::]?\s*\n([\s\S]*?)(?=\n-\s*\*\*|$)/i
2072
+ );
2073
+ const options = optionsSection ? parseOptionsBlock(optionsSection[1]) : [];
2074
+ return {
2075
+ code,
2076
+ questionId: code,
2077
+ pmQuestion,
2078
+ context: parseField(block, "\u573A\u666F"),
2079
+ options,
2080
+ technicalNote: parseField(block, "\u7814\u53D1\u5907\u6CE8"),
2081
+ reason: parseField(block, "\u539F\u56E0")
2082
+ };
2083
+ }
2084
+ function hasNoAssumptionsMarker(markdown) {
2085
+ return NO_ASSUMPTIONS_RE.test(markdown.replace(/\r\n/g, "\n"));
2086
+ }
2087
+ function countAssumptionMarkers(markdown) {
2088
+ const normalized = markdown.replace(/\r\n/g, "\n").trim();
2089
+ if (NO_ASSUMPTIONS_RE.test(normalized)) return 0;
2090
+ const codes = /* @__PURE__ */ new Set();
2091
+ for (const line of normalized.split("\n")) {
2092
+ const match = line.trim().match(/^####\s+(A\d+)\s*$/);
2093
+ if (match) codes.add(match[1]);
2094
+ }
2095
+ return codes.size;
2096
+ }
2097
+ function parseAssumptionsDocument(markdown, _source) {
2098
+ const normalized = markdown.replace(/\r\n/g, "\n").trim();
2099
+ if (!normalized || NO_ASSUMPTIONS_RE.test(normalized)) {
2100
+ return [];
2101
+ }
2102
+ const results = [];
2103
+ const blocks = normalized.split(/(?=^####\s+A\d+\s*$)/im);
2104
+ for (const block of blocks) {
2105
+ const headerMatch = block.match(BLOCK_HEADER_RE);
2106
+ if (!headerMatch) continue;
2107
+ const parsed = parseStructuredBlock(headerMatch[1], block);
2108
+ if (parsed) results.push(parsed);
2109
+ }
2110
+ return results;
2111
+ }
2112
+ function validateAssumptionForPm(assumption) {
2113
+ const issues = [];
2114
+ const questionForCheck = assumption.pmQuestion.replace(/`[^`]*`/g, "").trim();
2115
+ if (!assumption.pmQuestion?.trim()) {
2116
+ issues.push("\u7F3A\u5C11\u4E1A\u52A1\u95EE\u53E5");
2117
+ } else if (questionForCheck.length < 6) {
2118
+ issues.push("\u95EE\u53E5\u8FC7\u77ED");
2119
+ } else if (!BUSINESS_VERBS.test(questionForCheck)) {
2120
+ issues.push("\u95EE\u53E5\u7F3A\u5C11\u53EF\u7406\u89E3\u7684\u4E1A\u52A1\u52A8\u8BCD");
2121
+ }
2122
+ for (const { pattern, message } of DEV_PATTERNS) {
2123
+ if (pattern.test(questionForCheck)) {
2124
+ issues.push(message);
2125
+ }
2126
+ }
2127
+ if (!assumption.context?.trim()) {
2128
+ issues.push("\u7F3A\u5C11\u573A\u666F\u8BF4\u660E");
2129
+ }
2130
+ if (!assumption.options || assumption.options.length < 2) {
2131
+ issues.push("\u9009\u9879\u5C11\u4E8E 2 \u9879");
2132
+ }
2133
+ return { ok: issues.length === 0, issues };
2134
+ }
2135
+ function printAssumptionValidationResult(result) {
2136
+ for (const warning of result.warnings) {
2137
+ console.warn(`[apm] \u5047\u8BBE\u6821\u9A8C\u544A\u8B66: ${warning}`);
2138
+ }
2139
+ for (const item of result.issues) {
2140
+ console.error(
2141
+ `[apm] \u5047\u8BBE ${item.code} \u672A\u901A\u8FC7 PM \u6821\u9A8C: ${item.issues.join("\uFF1B")}`
2142
+ );
2143
+ }
2144
+ }
2145
+ function resolveAssumptionSourceFromFileName(fileName) {
2146
+ const key = fileName.replace(/\.md$/i, "").toUpperCase();
2147
+ if (key === "FRONTEND-ASSUMPTIONS") {
2148
+ return "FRONTEND_PLAN" /* FRONTEND_PLAN */;
2149
+ }
2150
+ if (key === "BACKEND-ASSUMPTIONS") {
2151
+ return "BACKEND_PLAN" /* BACKEND_PLAN */;
2152
+ }
2153
+ return null;
2154
+ }
2155
+ function validateAssumptionsMarkdown(markdown, source) {
2156
+ const markerCount = countAssumptionMarkers(markdown);
2157
+ const explicitEmpty = hasNoAssumptionsMarker(markdown);
2158
+ if (explicitEmpty && markerCount === 0) {
2159
+ return {
2160
+ ok: true,
2161
+ markerCount: 0,
2162
+ parsedCount: 0,
2163
+ issues: [],
2164
+ warnings: []
2165
+ };
2166
+ }
2167
+ const parsed = parseAssumptionsDocument(markdown, source);
2168
+ const issues = [];
2169
+ const warnings = [];
2170
+ for (const item of parsed) {
2171
+ const result = validateAssumptionForPm(item);
2172
+ if (!result.ok) {
2173
+ issues.push({ code: item.code, issues: result.issues });
2174
+ }
2175
+ }
2176
+ if (markerCount > 0 && parsed.length === 0) {
2177
+ warnings.push(
2178
+ `\u68C0\u6D4B\u5230 ${markerCount} \u6761\u5047\u8BBE\u6807\u8BB0\uFF0C\u4F46\u683C\u5F0F\u65E0\u6CD5\u89E3\u6790\u3002\u8BF7\u4F7F\u7528 #### A1 + \u95EE/\u573A\u666F/\u9009\u9879 \u7ED3\u6784\u3002`
2179
+ );
2180
+ } else if (markerCount > parsed.length) {
2181
+ warnings.push(
2182
+ `\u68C0\u6D4B\u5230 ${markerCount} \u6761\u5047\u8BBE\u6807\u8BB0\uFF0C\u4EC5\u89E3\u6790 ${parsed.length} \u6761\u3002`
2183
+ );
2184
+ }
2185
+ const ok = issues.length === 0 && warnings.length === 0 && (markerCount === 0 || parsed.length > 0);
2186
+ return {
2187
+ ok,
2188
+ markerCount,
2189
+ parsedCount: parsed.length,
2190
+ issues,
2191
+ warnings
2192
+ };
2193
+ }
2194
+
2035
2195
  // src/commands/sync-session-documents.ts
2036
2196
  import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
2037
2197
  import { join as join12 } from "path";
@@ -2116,6 +2276,19 @@ async function runSyncDocument(sessionId, options) {
2116
2276
  );
2117
2277
  process.exit(1);
2118
2278
  }
2279
+ const fileName = basename3(absPath);
2280
+ const assumptionSource = resolveAssumptionSourceFromFileName(fileName);
2281
+ if (assumptionSource) {
2282
+ const content = readFileSync9(absPath, "utf8");
2283
+ const validation = validateAssumptionsMarkdown(content, assumptionSource);
2284
+ if (!validation.ok) {
2285
+ printAssumptionValidationResult(validation);
2286
+ console.error(
2287
+ "[apm] \u5047\u8BBE sync \u672A\u901A\u8FC7\uFF0C\u5DF2\u4E2D\u6B62\u4E0A\u4F20\u3002\u8BF7\u6309 apm-write-assumptions \u6280\u80FD\u4FEE\u6B63\u540E\u91CD\u8BD5"
2288
+ );
2289
+ process.exit(1);
2290
+ }
2291
+ }
2119
2292
  const cfg = await ensureLoggedConfig();
2120
2293
  const api = createApmApiClient(cfg);
2121
2294
  const docsDir = sessionDocsDir(trimmedSessionId);
@@ -3005,7 +3178,7 @@ ${JSON.stringify(event, null, 2)}
3005
3178
  }
3006
3179
 
3007
3180
  // src/commands/connect/agent-session-registry.ts
3008
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "node:fs";
3181
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "node:fs";
3009
3182
  import { dirname as dirname4, resolve as resolve3 } from "node:path";
3010
3183
  function registryPath(workdir, sessionId) {
3011
3184
  return resolve3(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
@@ -3015,7 +3188,7 @@ function readRegistry(path13) {
3015
3188
  return {};
3016
3189
  }
3017
3190
  try {
3018
- const parsed = JSON.parse(readFileSync9(path13, "utf8"));
3191
+ const parsed = JSON.parse(readFileSync10(path13, "utf8"));
3019
3192
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3020
3193
  const result = {};
3021
3194
  for (const [key, value] of Object.entries(
@@ -3483,7 +3656,7 @@ async function ensureMessageHasReply(cfg, sessionId, messageId, fallback) {
3483
3656
  }
3484
3657
 
3485
3658
  // src/commands/connect/cli-version-sync.ts
3486
- import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "fs";
3659
+ import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
3487
3660
  import { join as join13 } from "path";
3488
3661
  var CLI_VERSION_FILE = ".cli-version.json";
3489
3662
  function manifestPath(apmDir) {
@@ -3496,7 +3669,7 @@ function loadManifest3(apmDir) {
3496
3669
  }
3497
3670
  try {
3498
3671
  const parsed = JSON.parse(
3499
- readFileSync10(path13, "utf8")
3672
+ readFileSync11(path13, "utf8")
3500
3673
  );
3501
3674
  if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
3502
3675
  return parsed;
@@ -3585,7 +3758,7 @@ function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT) {
3585
3758
  }
3586
3759
 
3587
3760
  // src/commands/connect-lock.ts
3588
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync12 } from "fs";
3761
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync12, unlinkSync, writeFileSync as writeFileSync12 } from "fs";
3589
3762
  import { join as join14 } from "path";
3590
3763
  var CONNECT_LOCK_PATH = join14(APM_CONFIG_DIR, "connect.lock");
3591
3764
  function isProcessAlive(pid) {
@@ -3600,7 +3773,7 @@ function isProcessAlive(pid) {
3600
3773
  function readConnectLock() {
3601
3774
  if (!existsSync13(CONNECT_LOCK_PATH)) return null;
3602
3775
  try {
3603
- const raw = readFileSync11(CONNECT_LOCK_PATH, "utf8");
3776
+ const raw = readFileSync12(CONNECT_LOCK_PATH, "utf8");
3604
3777
  const parsed = JSON.parse(raw);
3605
3778
  if (typeof parsed.pid !== "number" || parsed.mode !== "foreground" && parsed.mode !== "pm2" || typeof parsed.startedAt !== "string") {
3606
3779
  return null;
@@ -3656,7 +3829,6 @@ function forceReleaseConnectLock() {
3656
3829
  // src/commands/daemon.ts
3657
3830
  import { spawnSync as spawnSync2 } from "child_process";
3658
3831
  import { setTimeout as delay } from "node:timers/promises";
3659
- import { createRequire } from "node:module";
3660
3832
  import { existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync13 } from "fs";
3661
3833
  import { join as join15 } from "path";
3662
3834
  var PM2_APP_NAME = "apm-connect";
@@ -3699,11 +3871,110 @@ function formatConnectPm2EcosystemFile(ecosystem) {
3699
3871
  return `module.exports = ${JSON.stringify(ecosystem, null, 2)};
3700
3872
  `;
3701
3873
  }
3702
- function resolvePm2Bin() {
3703
- const require2 = createRequire(import.meta.url);
3704
- return require2.resolve("pm2/bin/pm2");
3705
- }
3706
3874
  var useNpmShell2 = process.platform === "win32";
3875
+ function runNpm2(args, options = {}) {
3876
+ return spawnSync2(useNpmShell2 ? "npm.cmd" : "npm", args, {
3877
+ ...options,
3878
+ shell: useNpmShell2
3879
+ });
3880
+ }
3881
+ function verifyPm2Bin(pm2Bin) {
3882
+ if (!pm2Bin.trim() || !existsSync14(pm2Bin)) {
3883
+ return false;
3884
+ }
3885
+ const result = spawnSync2(pm2Bin, ["--version"], {
3886
+ encoding: "utf8",
3887
+ shell: useNpmShell2,
3888
+ stdio: ["ignore", "pipe", "pipe"],
3889
+ timeout: 15e3
3890
+ });
3891
+ return !result.error && result.status === 0;
3892
+ }
3893
+ function findGlobalPm2() {
3894
+ const candidates = [];
3895
+ const whichResult = spawnSync2(useNpmShell2 ? "where" : "which", ["pm2"], {
3896
+ encoding: "utf8",
3897
+ shell: useNpmShell2,
3898
+ stdio: ["ignore", "pipe", "pipe"]
3899
+ });
3900
+ if (whichResult.status === 0) {
3901
+ const lines = whichResult.stdout?.toString().trim().split(/\r?\n/) ?? [];
3902
+ for (const line of lines) {
3903
+ const candidate = line.trim();
3904
+ if (candidate) {
3905
+ candidates.push(candidate);
3906
+ }
3907
+ }
3908
+ }
3909
+ const binResult = runNpm2(["bin", "-g"], {
3910
+ encoding: "utf8",
3911
+ stdio: ["ignore", "pipe", "pipe"]
3912
+ });
3913
+ if (binResult.status === 0) {
3914
+ const globalBin = binResult.stdout?.toString().trim();
3915
+ if (globalBin) {
3916
+ candidates.push(join15(globalBin, useNpmShell2 ? "pm2.cmd" : "pm2"));
3917
+ }
3918
+ }
3919
+ const seen = /* @__PURE__ */ new Set();
3920
+ for (const candidate of candidates) {
3921
+ if (seen.has(candidate)) continue;
3922
+ seen.add(candidate);
3923
+ if (verifyPm2Bin(candidate)) {
3924
+ return candidate;
3925
+ }
3926
+ }
3927
+ return null;
3928
+ }
3929
+ function installGlobalPm2() {
3930
+ console.log("[apm] \u672A\u68C0\u6D4B\u5230\u5168\u5C40 pm2\uFF0C\u6B63\u5728\u5B89\u88C5 npm install -g pm2 \u2026");
3931
+ const result = runNpm2(["install", "-g", "pm2"], {
3932
+ encoding: "utf8",
3933
+ stdio: "inherit"
3934
+ });
3935
+ if (result.status !== 0) {
3936
+ console.error("[apm] \u5B89\u88C5 pm2 \u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: npm install -g pm2");
3937
+ process.exit(result.status ?? 1);
3938
+ }
3939
+ }
3940
+ function readPm2Version(pm2Bin) {
3941
+ const result = spawnSync2(pm2Bin, ["--version"], {
3942
+ encoding: "utf8",
3943
+ shell: useNpmShell2,
3944
+ stdio: ["ignore", "pipe", "pipe"],
3945
+ timeout: 15e3
3946
+ });
3947
+ if (result.error || result.status !== 0) {
3948
+ return null;
3949
+ }
3950
+ return result.stdout?.toString().trim() || null;
3951
+ }
3952
+ function logPm2Ready(pm2Bin, installed2) {
3953
+ const version = readPm2Version(pm2Bin);
3954
+ const versionSuffix = version ? ` ${version}` : "";
3955
+ if (installed2) {
3956
+ console.log(`[apm] \u5168\u5C40 pm2 \u5DF2\u5B89\u88C5${versionSuffix}: ${pm2Bin}`);
3957
+ } else {
3958
+ console.log(`[apm] \u5DF2\u68C0\u6D4B\u5230\u5168\u5C40 pm2${versionSuffix}: ${pm2Bin}`);
3959
+ }
3960
+ }
3961
+ function ensureGlobalPm2() {
3962
+ const existing = findGlobalPm2();
3963
+ if (existing) {
3964
+ logPm2Ready(existing, false);
3965
+ return existing;
3966
+ }
3967
+ installGlobalPm2();
3968
+ const installed2 = findGlobalPm2();
3969
+ if (!installed2) {
3970
+ console.error(
3971
+ "[apm] \u5B89\u88C5 pm2 \u540E\u4ECD\u65E0\u6CD5\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: npm install -g pm2"
3972
+ );
3973
+ process.exit(1);
3974
+ }
3975
+ logPm2Ready(installed2, true);
3976
+ return installed2;
3977
+ }
3707
3978
  function resolveApmEntryPath(entryArg = process.argv[1]) {
3708
3979
  const fromArgv = entryArg?.trim();
3709
3980
  if (fromArgv && existsSync14(fromArgv)) {
@@ -3733,20 +4004,13 @@ function isRunningUnderPm2() {
3733
4004
  return process.env.name === PM2_APP_NAME && process.env.pm_id !== void 0;
3734
4005
  }
3735
4006
  function runPm2(args, options) {
3736
- let pm2Bin;
3737
- try {
3738
- pm2Bin = resolvePm2Bin();
3739
- } catch {
3740
- console.error(
3741
- "[apm] \u672A\u627E\u5230\u5185\u7F6E pm2\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5 apm CLI\uFF1Anpm install -g ai-project-manage-cli@latest"
3742
- );
3743
- process.exit(1);
3744
- }
4007
+ const pm2Bin = ensureGlobalPm2();
3745
4008
  const spawnOptions = {
3746
4009
  encoding: "utf8",
3747
- stdio: options?.inherit ? "inherit" : ["ignore", "pipe", "pipe"]
4010
+ stdio: options?.inherit ? "inherit" : ["ignore", "pipe", "pipe"],
4011
+ shell: useNpmShell2
3748
4012
  };
3749
- const result = spawnSync2(process.execPath, [pm2Bin, ...args], spawnOptions);
4013
+ const result = spawnSync2(pm2Bin, args, spawnOptions);
3750
4014
  if (result.error) {
3751
4015
  console.error("[apm] \u6267\u884C pm2 \u5931\u8D25:", result.error.message);
3752
4016
  process.exit(1);
@@ -3760,15 +4024,14 @@ function runPm2(args, options) {
3760
4024
  }
3761
4025
  }
3762
4026
  function runPm2Json(args) {
3763
- let pm2Bin;
3764
- try {
3765
- pm2Bin = resolvePm2Bin();
3766
- } catch {
4027
+ const pm2Bin = findGlobalPm2();
4028
+ if (!pm2Bin) {
3767
4029
  return "[]";
3768
4030
  }
3769
- const result = spawnSync2(process.execPath, [pm2Bin, ...args], {
4031
+ const result = spawnSync2(pm2Bin, args, {
3770
4032
  encoding: "utf8",
3771
- stdio: ["ignore", "pipe", "pipe"]
4033
+ stdio: ["ignore", "pipe", "pipe"],
4034
+ shell: useNpmShell2
3772
4035
  });
3773
4036
  if (result.status !== 0) return "[]";
3774
4037
  return result.stdout?.toString() ?? "[]";
@@ -3865,6 +4128,7 @@ async function prepareEcosystem(server) {
3865
4128
  async function runDaemonStart(options) {
3866
4129
  assertConnectNotRunning();
3867
4130
  await runUpdate();
4131
+ ensureGlobalPm2();
3868
4132
  const cfg = await prepareEcosystem(options.server);
3869
4133
  runPm2(["start", PM2_ECOSYSTEM_PATH, "--update-env"], { inherit: true });
3870
4134
  console.log(
@@ -4534,7 +4798,7 @@ import { spawnSync as spawnSync5 } from "node:child_process";
4534
4798
  import path5 from "node:path";
4535
4799
 
4536
4800
  // src/commands/deploy/internal/apm-config.ts
4537
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
4801
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
4538
4802
  import { homedir as homedir2 } from "node:os";
4539
4803
  import { join as join16, resolve as resolve4 } from "node:path";
4540
4804
  function loadApmConfig(options) {
@@ -4547,7 +4811,7 @@ function loadApmConfig(options) {
4547
4811
  process.exit(1);
4548
4812
  }
4549
4813
  try {
4550
- const raw = readFileSync12(p, "utf8");
4814
+ const raw = readFileSync13(p, "utf8");
4551
4815
  return JSON.parse(raw);
4552
4816
  } catch (e) {
4553
4817
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -4736,7 +5000,7 @@ function readMavenLocalRepoFromSettings() {
4736
5000
  return null;
4737
5001
  }
4738
5002
  try {
4739
- const xml = readFileSync12(settingsPath, "utf8");
5003
+ const xml = readFileSync13(settingsPath, "utf8");
4740
5004
  const match = xml.match(
4741
5005
  /<localRepository>\s*([^<]+?)\s*<\/localRepository>/
4742
5006
  );
@@ -4878,7 +5142,7 @@ var DeployExecutionError = class extends Error {
4878
5142
  };
4879
5143
 
4880
5144
  // src/commands/deploy/internal/wisdom-auto-deploy.ts
4881
- import { existsSync as existsSync17, readFileSync as readFileSync14, statSync as statSync7 } from "node:fs";
5145
+ import { existsSync as existsSync17, readFileSync as readFileSync15, statSync as statSync7 } from "node:fs";
4882
5146
  import path4 from "node:path";
4883
5147
  import { spawnSync as spawnSync4 } from "node:child_process";
4884
5148
 
@@ -4887,7 +5151,7 @@ import {
4887
5151
  existsSync as existsSync16,
4888
5152
  mkdirSync as mkdirSync8,
4889
5153
  readdirSync as readdirSync5,
4890
- readFileSync as readFileSync13,
5154
+ readFileSync as readFileSync14,
4891
5155
  statSync as statSync6,
4892
5156
  writeFileSync as writeFileSync14
4893
5157
  } from "node:fs";
@@ -5157,7 +5421,7 @@ function loadManifest4() {
5157
5421
  if (!existsSync16(manifestPath2)) {
5158
5422
  return {};
5159
5423
  }
5160
- return JSON.parse(readFileSync13(manifestPath2, "utf8"));
5424
+ return JSON.parse(readFileSync14(manifestPath2, "utf8"));
5161
5425
  }
5162
5426
  function saveManifest4(manifest) {
5163
5427
  const dir = deployCacheDir();
@@ -5223,7 +5487,7 @@ async function createUpdatePackage(entries, packageName) {
5223
5487
  log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path3.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
5224
5488
  const zip = new JSZip2();
5225
5489
  for (const entry of entries) {
5226
- const content = readFileSync13(entry.path);
5490
+ const content = readFileSync14(entry.path);
5227
5491
  zip.file(entry.arcname, content);
5228
5492
  log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
5229
5493
  }
@@ -5789,7 +6053,7 @@ function readPackageScripts(cwd) {
5789
6053
  return {};
5790
6054
  }
5791
6055
  try {
5792
- const raw = readFileSync14(pkgPath, "utf8");
6056
+ const raw = readFileSync15(pkgPath, "utf8");
5793
6057
  const parsed = JSON.parse(raw);
5794
6058
  return parsed.scripts ?? {};
5795
6059
  } catch {
@@ -6313,7 +6577,7 @@ import path9 from "node:path";
6313
6577
  import Docker from "dockerode";
6314
6578
 
6315
6579
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
6316
- import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
6580
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
6317
6581
  import path6 from "node:path";
6318
6582
  function asOptionalTlsBuffer(value) {
6319
6583
  if (typeof value !== "string") {
@@ -6326,7 +6590,7 @@ function asOptionalTlsBuffer(value) {
6326
6590
  return void 0;
6327
6591
  }
6328
6592
  if (existsSync18(normalized)) {
6329
- return readFileSync15(normalized);
6593
+ return readFileSync16(normalized);
6330
6594
  }
6331
6595
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
6332
6596
  if (looksLikePath) {
@@ -6536,7 +6800,7 @@ var DockerodeClient = class {
6536
6800
  var createDockerodeClient = (config) => new DockerodeClient(config);
6537
6801
 
6538
6802
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
6539
- import { existsSync as existsSync19, readFileSync as readFileSync16, statSync as statSync8 } from "node:fs";
6803
+ import { existsSync as existsSync19, readFileSync as readFileSync17, statSync as statSync8 } from "node:fs";
6540
6804
  import path7 from "node:path";
6541
6805
  function stripSurroundingQuotes(value) {
6542
6806
  const t = value.trim();
@@ -6556,7 +6820,7 @@ function loadEnvFromFile(envFilePath) {
6556
6820
  if (!existsSync19(targetPath) || !statSync8(targetPath).isFile()) {
6557
6821
  return {};
6558
6822
  }
6559
- const raw = readFileSync16(targetPath, "utf-8");
6823
+ const raw = readFileSync17(targetPath, "utf-8");
6560
6824
  const result = {};
6561
6825
  for (const line of raw.split(/\r?\n/)) {
6562
6826
  const normalized = line.trim();
@@ -7217,7 +7481,7 @@ function buildProgram() {
7217
7481
  "\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09\uFF0C\u7EF4\u6301\u5FC3\u8DF3\u5E76\u5904\u7406\u4E0B\u884C message\uFF08TYPING \u2192 Cursor \u2192 SUCCESS/FAILED\uFF09\uFF1B\u542F\u52A8\u524D\u81EA\u52A8 apm update \u5230\u5F53\u524D\u5927\u7248\u672C\u6700\u65B0\u7248"
7218
7482
  ).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").option(
7219
7483
  "--daemon",
7220
- "\u540E\u53F0\u5B88\u62A4\u8FD0\u884C\uFF08\u5185\u7F6E PM2\uFF0C\u65AD\u7EBF\u6216\u9000\u51FA\u540E\u81EA\u52A8\u91CD\u542F\uFF1B\u63A8\u8350\u7528\u4E8E\u957F\u671F\u5728\u7EBF\uFF09"
7484
+ "\u540E\u53F0\u5B88\u62A4\u8FD0\u884C\uFF08\u5168\u5C40 PM2\uFF0C\u65AD\u7EBF\u6216\u9000\u51FA\u540E\u81EA\u52A8\u91CD\u542F\uFF1B\u63A8\u8350\u7528\u4E8E\u957F\u671F\u5728\u7EBF\uFF09"
7221
7485
  ).option("-f, --follow", "\u4E0E --daemon \u5408\u7528\uFF1A\u542F\u52A8\u540E\u5728\u5F53\u524D\u7EC8\u7AEF\u5B9E\u65F6\u8DDF\u8E2A\u65E5\u5FD7").action(
7222
7486
  async (opts) => {
7223
7487
  if (opts.follow && !opts.daemon) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.94",
3
+ "version": "6.0.96",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -44,7 +44,6 @@
44
44
  "dockerode": "~5.0.0",
45
45
  "ssh2": "~1.16.0",
46
46
  "ssh2-sftp-client": "~12.0.1",
47
- "jszip": "~3.10.1",
48
- "pm2": "~5.4.3"
47
+ "jszip": "~3.10.1"
49
48
  }
50
49
  }
@@ -0,0 +1,78 @@
1
+ # apm-write-assumptions:编写并同步待确认口径
2
+
3
+ ## 何时使用
4
+
5
+ `apm-write-plan` 步骤 3 编写 `*-ASSUMPTIONS.md` 时;或需重写/补写假设文档时。
6
+
7
+ | 角色 | 产出 |
8
+ | ---- | ------------------------------ |
9
+ | 前端 | `docs/FRONTEND-ASSUMPTIONS.md` |
10
+ | 后端 | `docs/BACKEND-ASSUMPTIONS.md` |
11
+
12
+ ---
13
+
14
+ ## 工作流程
15
+
16
+ ### 1. 按模板编写
17
+
18
+ 1. **Read** `.apm/skills/apm-write-assumptions/assumptions-template.md`
19
+ 2. **Write** 本端 `docs/*-ASSUMPTIONS.md`(写作时对照下方 **PM 校验规则**)
20
+ 3. 无假设时正文仅一行:`无,口径均有依据。`
21
+
22
+ ### 2. 直接 sync
23
+
24
+ ```bash
25
+ apm sync-document <sessionId> --file FRONTEND-ASSUMPTIONS.md
26
+ # 或 BACKEND-ASSUMPTIONS.md
27
+ ```
28
+
29
+ **成功**须看到:
30
+
31
+ ```text
32
+ [apm] 假设解析: 标记 N 条,入库 N 条(PM 可确认 N 条)
33
+ ```
34
+
35
+ 无假设时无 `假设解析` 行,属正常。
36
+
37
+ ### 3. 报错则改完重 sync
38
+
39
+ `sync-document` 失败时会打印具体假设编号与原因(如 `假设 A2 未通过 PM 校验: …`)。
40
+
41
+ 1. 按报错修正对应 `A*` 的 **问 / 场景 / 选项**(规则见下)
42
+ 2. 再次执行步骤 2
43
+ 3. **禁止**在未成功 sync 前 @ 项目经理、sync PLAN、或声称「请在 Web 待确认」
44
+
45
+ ---
46
+
47
+ ## PM 校验规则(与服务端一致)
48
+
49
+ 每条假设(`#### A1` …)必须包含:
50
+
51
+ | 字段 | 要求 |
52
+ | ------------ | --------------------------------------------------------------- |
53
+ | **问** | 业务语言;≥6 字;含下列业务动词之一;禁止字段名/路径/SQL/反引号 |
54
+ | **场景** | 非空 |
55
+ | **选项** | ≥2 项,格式 `- opt_id:说明` |
56
+ | **研发备注** | 可选;技术细节放这里,**不要**写进「问」 |
57
+
58
+ **问句须含以下动词之一**:
59
+
60
+ `提交` `展示` `校验` `驳回` `审批` `允许` `禁止` `筛选` `列表` `详情` `暂存` `删除` `新增` `修改` `导出` `导入`
61
+
62
+ **问句禁止**:`status=`、文件扩展名、反引号代码、SQL、`类.字段`、snake_case 字段名。
63
+
64
+ ### 正反例
65
+
66
+ | 假设 | 问句 | 结果 |
67
+ | ------- | ---------------------------------------------- | ---------------------- |
68
+ | A2 | 编辑已有记录时,「落实人员」应如何处理? | **失败**(无业务动词) |
69
+ | A2 修正 | 编辑已有记录时,是否**允许修改**「落实人员」? | 通过 |
70
+ | A3 | 「落实人员」可选择的范围是? | **失败** |
71
+ | A3 修正 | 「落实人员」下拉**列表**应**展示**哪些人员? | 通过 |
72
+
73
+ ---
74
+
75
+ ## 与其他技能
76
+
77
+ - 由 **`apm-write-plan`** 在 sync PLAN 前调用本技能
78
+ - PM 确认后由 **`apm-update-plan`** 更新 PLAN
@@ -2,9 +2,11 @@
2
2
 
3
3
  > 本文件由 apm sync-document 写入;PM 确认后服务端回写「状态/确认结果」。
4
4
  > **禁止**手动编辑;已确认条目 AI 禁止修改。
5
+ > 编写与校验规则见 `.apm/skills/apm-write-assumptions/SKILL.md`。
5
6
 
6
7
  #### A1
7
- - **问**:(业务语言问句,禁止字段名/文件路径)
8
+
9
+ - **问**:(业务语言问句;须含:提交/展示/校验/驳回/审批/允许/禁止/筛选/列表/详情/暂存/删除/新增/修改/导出/导入 之一)
8
10
  - **场景**:(需求未说明的背景)
9
11
  - **选项**:
10
12
  - opt_a:(选项说明)
@@ -4,11 +4,11 @@
4
4
 
5
5
  前端 / 后端工程师在任务启动后**直接读原始需求写实现计划**,不经过 PRD。
6
6
 
7
- | 角色 | 产出文档 | 路径 |
8
- | ---- | -------- | ---- |
9
- | 后端 | `BACKEND-PLAN.md` + `API.md` | `docs/BACKEND-PLAN.md`、`docs/API.md` |
7
+ | 角色 | 产出文档 | 路径 |
8
+ | ---- | ---------------------------------------------- | ------------------------------------------------------- |
9
+ | 后端 | `BACKEND-PLAN.md` + `API.md` | `docs/BACKEND-PLAN.md`、`docs/API.md` |
10
10
  | 前端 | `FRONTEND-PLAN.md` + `FRONTEND-ASSUMPTIONS.md` | `docs/FRONTEND-PLAN.md`、`docs/FRONTEND-ASSUMPTIONS.md` |
11
- | 后端 | 同上 | `docs/BACKEND-ASSUMPTIONS.md` |
11
+ | 后端 | 同上 | `docs/BACKEND-ASSUMPTIONS.md` |
12
12
 
13
13
  ---
14
14
 
@@ -27,27 +27,18 @@
27
27
  2. 代码调研预算:**最多 15 个文件**。
28
28
  3. 口径不清 → 记入 **ASSUMPTIONS 文件**,禁止编造。
29
29
 
30
- ### 步骤 3:按模板写计划与假设
30
+ ### 步骤 3:写计划与假设
31
31
 
32
32
  1. **Read** `.apm/skills/apm-write-plan/plan-template.md`,**Write** 本端 `*-PLAN.md`(**不含**假设章节)。
33
- 2. **Read** `.apm/skills/apm-write-plan/assumptions-template.md`,**Write** 本端 `*-ASSUMPTIONS.md`。
34
- 3. 无假设时在 ASSUMPTIONS 中写「无,口径均有依据」。
33
+ 2. **Read** 并完整执行 **`.apm/skills/apm-write-assumptions/SKILL.md`**(编写 `*-ASSUMPTIONS.md` → sync;报错则改完重 sync)。
35
34
 
36
- ### 步骤 3.5:假设 PM 友好自检(sync 前必做)
35
+ ### 步骤 4:同步 PLAN 与回复
37
36
 
38
- 每条假设必须含 **问 / 场景 / 选项(≥2 项,id + label)**:
37
+ 1. `apm sync-document --file FRONTEND-PLAN.md`(或 BACKEND)。
38
+ 2. **有假设且 ASSUMPTIONS 已成功 sync**:@项目经理 请其在 Web **「待您确认」** Panel 点选;说明「以上假设确认前不开始开发」。
39
+ 3. **无假设**:`append_message` 发送「本端需求理解」摘要并 @项目经理 请确认。
39
40
 
40
- - **问** 禁止:`status=`、字段名、表名、类路径、SQL。
41
- - **禁止多选**;复合口径由 PM 在 Web「其他」中填写。
42
- - 能自己查清的代码含义 → 移入 PLAN「依据」,删除假设。
43
- - 技术发现 → 写进 **研发备注**,问句用业务语言。
44
-
45
- ### 步骤 4:同步与回复
46
-
47
- 1. `apm sync-document --file FRONTEND-ASSUMPTIONS.md`(或 BACKEND)**先于** PLAN。
48
- 2. `apm sync-document --file FRONTEND-PLAN.md`(或 BACKEND)。
49
- 3. **有假设**:@项目经理 请其在 Web **「待您确认」** Panel 点选;说明「以上假设确认前不开始开发」。
50
- 4. **无假设**:`append_message` 发送「本端需求理解」摘要并 @项目经理 请确认。
41
+ > **禁止**在 ASSUMPTIONS sync 未成功时 @ 项目经理确认假设;PLAN 中「待确认 N 项」由服务端 sync ASSUMPTIONS 后自动更新,AI 不要手写具体数字。
51
42
 
52
43
  ### 假设确认后更新计划
53
44
 
@@ -1,26 +0,0 @@
1
- ## 工作流程
2
-
3
- ### 步骤 1: 获取当前版本 PRD 的内容
4
-
5
- 先用 **Read** 工具阅读 `.apm/sessions/<会话ID>/docs/PRD.md`,如果存在则这个目录下的内容为需求文档,否则用 **Read** 工具阅读 `.apm/sessions/<会话ID>/TASK.md`,并把这个文件里面的内容视为需求文档
6
-
7
- ### 步骤 2: 理解代码,给出评审意见,具体要求如下:
8
-
9
- #### 评审目标如下:
10
-
11
- 1. 梳理需求边界:发现文档中未写清的口径
12
- 2. 调研实现难度:在实现难度较高时给出提示
13
- 3. 禁止为了凑问题而提问,当 PRD 已足够清晰且无高难度改造时,可直接说此任务没问题
14
- 4. 你只需要站在自己的角度去评审,禁止站在全局思考问题。结合代码进行评审即可:
15
- - 前端关注 UI 交互还原,交互逻辑的各种边界,不考虑一些与后端接口之类的问题
16
- - 后端则关注数据表设计,需求对应的数据接口是否合理,能否获取
17
- - 全栈则关注前面两者,可以把这些问题合并起来提出来
18
-
19
- **难度高的定义**:预估改动的文件可能比较多,超过 10 个;或者改动逻辑比较复杂;
20
-
21
- #### 表达规范如下
22
-
23
- 1. **产品语言优先**:页面定位用「医德考评弹窗」「行风办审批页」等说法;**禁止**用文件路径、组件名、函数名作为论据主体。
24
- 2. **后端可略宽**:必要时可写**表名 / 主表字段名**帮助产品理解数据口径,但仍避免贴大段代码或接口路径。
25
- 3. **锚定 PRD**:当需要引用到任务文档中的某一条时可以直接说行号,比如:需求原文[1-3]说 xxx;
26
- 4. **问题 = 文档缺口**:只写 PRD **未写清**且**影响本端理解边界**的点。已写清楚的规则不要重复质疑。
@@ -1,12 +0,0 @@
1
- # apm-rewrite-assumptions:重写 PM 不友好的假设问法
2
-
3
- ## 何时使用
4
-
5
- `apm sync-document` 同步 ASSUMPTIONS 失败(问法校验不通过),或计划被退回需重写「问 / 场景 / 选项」。
6
-
7
- ## 工作流程
8
-
9
- 1. **Read** 本端 `docs/FRONTEND-ASSUMPTIONS.md` 或 `docs/BACKEND-ASSUMPTIONS.md`(及 sync 报错中的 qualityIssues)。
10
- 2. 将研发术语移入 **研发备注**;**问** 改为 PM 可理解的业务句子;补全 **场景** 与 **≥2 个选项**(含 id + label)。
11
- 3. **禁止多选**;复合口径说明写在 Other 指引中。
12
- 4. 重新 **`apm sync-document --file *-ASSUMPTIONS.md`** 同步。