ai-project-manage-cli 7.1.37 → 7.1.38

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
@@ -1000,8 +1000,8 @@ import { Command } from "commander";
1000
1000
  init_config();
1001
1001
 
1002
1002
  // src/commands/init.ts
1003
- import { join as join6 } from "path";
1004
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
1003
+ import { join as join3 } from "path";
1004
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1005
1005
 
1006
1006
  // src/command-utils.ts
1007
1007
  init_config();
@@ -1213,10 +1213,52 @@ async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
1213
1213
  assertTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
1214
1214
  }
1215
1215
 
1216
+ // src/commands/init.ts
1217
+ async function initializeWorkspace(workdir, options) {
1218
+ await ensureWorkspaceApmDirForInit(workdir);
1219
+ const apmDir = workspaceApmDir(workdir);
1220
+ await copyTemplateFiles(apmDir, workdir);
1221
+ const trimmedName = options?.name?.trim();
1222
+ if (trimmedName) {
1223
+ const apmConfigPath = toFsPath(join3(apmDir, "apm.config.json"));
1224
+ const config = readFileSync2(apmConfigPath, "utf8");
1225
+ const configJson = JSON.parse(config);
1226
+ configJson.name = trimmedName;
1227
+ writeFileSync2(
1228
+ apmConfigPath,
1229
+ `${JSON.stringify(configJson, null, 2)}
1230
+ `,
1231
+ "utf8"
1232
+ );
1233
+ }
1234
+ console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
1235
+ return { didInit: true };
1236
+ }
1237
+ async function ensureWorkspaceInitialized(workdir, options) {
1238
+ if (isWorkspaceApmInitialized(workdir)) {
1239
+ return { didInit: false };
1240
+ }
1241
+ console.log(
1242
+ `[apm] \u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u672A\u68C0\u6D4B\u5230\u5DF2\u521D\u59CB\u5316\u7684 .apm\uFF0C\u6B63\u5728\u81EA\u52A8\u521D\u59CB\u5316\u2026`
1243
+ );
1244
+ return initializeWorkspace(workdir, options);
1245
+ }
1246
+ async function runInit(name) {
1247
+ const workdir = resolveWorkdirPath();
1248
+ await initializeWorkspace(workdir, { name });
1249
+ console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
1250
+ }
1251
+
1252
+ // src/commands/login.ts
1253
+ init_config();
1254
+ init_client();
1255
+ import { existsSync as existsSync3 } from "fs";
1256
+ import { ApiError } from "listpage-http";
1257
+
1216
1258
  // src/deployment-config-sync.ts
1217
1259
  init_client();
1218
- import { join as join4 } from "path";
1219
- import { writeFileSync as writeFileSync3 } from "fs";
1260
+ import { join as join5 } from "path";
1261
+ import { writeFileSync as writeFileSync4 } from "fs";
1220
1262
 
1221
1263
  // src/git-remote.ts
1222
1264
  import { execFile } from "child_process";
@@ -1455,16 +1497,16 @@ init_config();
1455
1497
  import {
1456
1498
  existsSync as existsSync2,
1457
1499
  mkdirSync as mkdirSync3,
1458
- readFileSync as readFileSync2,
1500
+ readFileSync as readFileSync3,
1459
1501
  readdirSync as readdirSync2,
1460
1502
  statSync as statSync2,
1461
- writeFileSync as writeFileSync2
1503
+ writeFileSync as writeFileSync3
1462
1504
  } from "fs";
1463
- import { basename as basename2, dirname as dirname2, join as join3, relative, resolve as resolve3 } from "path";
1505
+ import { basename as basename2, dirname as dirname2, join as join4, relative, resolve as resolve3 } from "path";
1464
1506
  var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
1465
1507
  var WORKSPACE_REPOS_VERSION = 1;
1466
1508
  function manifestPath(workdir) {
1467
- return join3(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
1509
+ return join4(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
1468
1510
  }
1469
1511
  function absoluteRepoPath(workdir, entry) {
1470
1512
  if (entry.path === "." || entry.path === "") {
@@ -1493,7 +1535,7 @@ function readManifest(workdir) {
1493
1535
  }
1494
1536
  try {
1495
1537
  const raw = JSON.parse(
1496
- readFileSync2(path19, "utf8")
1538
+ readFileSync3(path19, "utf8")
1497
1539
  );
1498
1540
  if (raw?.version !== WORKSPACE_REPOS_VERSION) {
1499
1541
  return null;
@@ -1526,7 +1568,7 @@ function writeManifest(workdir, manifest) {
1526
1568
  const apmDir = toFsPath(workspaceApmDir(workdir));
1527
1569
  mkdirSync3(apmDir, { recursive: true });
1528
1570
  const path19 = toFsPath(manifestPath(workdir));
1529
- writeFileSync2(path19, `${JSON.stringify(manifest, null, 2)}
1571
+ writeFileSync3(path19, `${JSON.stringify(manifest, null, 2)}
1530
1572
  `, "utf8");
1531
1573
  }
1532
1574
  function isPathInsideOrEqual(parentAbs, childAbs) {
@@ -1679,8 +1721,8 @@ async function writeDeploymentConfigContent(apmDir, content, configName) {
1679
1721
  );
1680
1722
  return false;
1681
1723
  }
1682
- const apmConfigPath = toFsPath(join4(apmDir, "apm.config.json"));
1683
- writeFileSync3(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
1724
+ const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
1725
+ writeFileSync4(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
1684
1726
  `, "utf8");
1685
1727
  console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${configName}`);
1686
1728
  console.log("[apm] \u5DF2\u5199\u5165 .apm/apm.config.json");
@@ -1732,324 +1774,7 @@ ${diagnostic ?? ""}
1732
1774
  return { synced: true, repositoryId, configName: config.name };
1733
1775
  }
1734
1776
 
1735
- // src/project-documents-sync.ts
1736
- init_client();
1737
- init_config();
1738
- import {
1739
- existsSync as existsSync3,
1740
- readdirSync as readdirSync3,
1741
- readFileSync as readFileSync3,
1742
- rmSync,
1743
- writeFileSync as writeFileSync4
1744
- } from "fs";
1745
- import { createHash } from "crypto";
1746
- import { dirname as dirname3, join as join5, relative as relative2, sep } from "path";
1747
- var MANIFEST_FILE = "manifest.json";
1748
- function normalizeProjectIdForPath(projectId) {
1749
- const id = projectId.trim();
1750
- if (!id) {
1751
- throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
1752
- }
1753
- if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
1754
- throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
1755
- }
1756
- return id;
1757
- }
1758
- function projectDocumentsDir(apmRoot, projectId) {
1759
- const id = normalizeProjectIdForPath(projectId);
1760
- return join5(apmRoot ?? workspaceApmDir(), "project", id);
1761
- }
1762
- function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
1763
- const normalized = normalizeLocalDocumentPath(documentPath);
1764
- return join5(
1765
- projectDocumentsDir(apmRoot, projectId),
1766
- ...normalized.split("/")
1767
- );
1768
- }
1769
- function normalizeLocalDocumentPath(path19) {
1770
- const trimmed = path19.trim().replace(/\\/g, "/");
1771
- if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
1772
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
1773
- }
1774
- const segments = trimmed.split("/").filter(Boolean);
1775
- if (segments.some((segment) => segment === ".." || segment === ".")) {
1776
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
1777
- }
1778
- return segments.join("/");
1779
- }
1780
- function hashLocalFileContent(content) {
1781
- return createHash("sha256").update(content, "utf8").digest("hex");
1782
- }
1783
- function readLocalManifest(apmRoot, projectId) {
1784
- const manifestPath3 = join5(
1785
- projectDocumentsDir(apmRoot, projectId),
1786
- MANIFEST_FILE
1787
- );
1788
- if (!existsSync3(manifestPath3)) {
1789
- return null;
1790
- }
1791
- try {
1792
- return JSON.parse(
1793
- readFileSync3(manifestPath3, "utf8")
1794
- );
1795
- } catch {
1796
- return null;
1797
- }
1798
- }
1799
- function listLocalDocumentPaths(apmRoot, projectId) {
1800
- const root = projectDocumentsDir(apmRoot, projectId);
1801
- if (!existsSync3(root)) {
1802
- return [];
1803
- }
1804
- const paths = [];
1805
- const walk = (dir) => {
1806
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
1807
- const abs = join5(dir, entry.name);
1808
- if (entry.isDirectory()) {
1809
- walk(abs);
1810
- continue;
1811
- }
1812
- if (entry.isFile() && entry.name === MANIFEST_FILE) {
1813
- continue;
1814
- }
1815
- const rel = relative2(root, abs).split(sep).join("/");
1816
- paths.push(rel);
1817
- }
1818
- };
1819
- walk(root);
1820
- return paths.sort();
1821
- }
1822
- function diffManifestPaths(remote, local) {
1823
- const remoteMap = new Map(
1824
- (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
1825
- );
1826
- const localMap = new Map(
1827
- (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
1828
- );
1829
- const download = [];
1830
- for (const [path19, hash] of remoteMap) {
1831
- if (localMap.get(path19) !== hash) {
1832
- download.push(path19);
1833
- }
1834
- }
1835
- const deleteLocal = [];
1836
- for (const path19 of localMap.keys()) {
1837
- if (!remoteMap.has(path19)) {
1838
- deleteLocal.push(path19);
1839
- }
1840
- }
1841
- return { download, deleteLocal };
1842
- }
1843
- async function resolveProjectIdForSync(api, workdirPath, options) {
1844
- const explicit = options?.projectId?.trim();
1845
- if (explicit) {
1846
- return { projectId: explicit, diagnostic: null };
1847
- }
1848
- try {
1849
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
1850
- const projectId = baseline.projectId?.trim() || null;
1851
- if (projectId) {
1852
- return { projectId, diagnostic: null };
1853
- }
1854
- const projectIds = baseline.projectIds ?? [];
1855
- if (projectIds.length === 0) {
1856
- return {
1857
- projectId: null,
1858
- diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
1859
- \u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
1860
- };
1861
- }
1862
- return {
1863
- projectId: null,
1864
- diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
1865
- \u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
1866
- };
1867
- } catch (err) {
1868
- const detail = err instanceof Error ? err.message : String(err);
1869
- return {
1870
- projectId: null,
1871
- diagnostic: detail.replace(/^\[apm\]\s*/, "")
1872
- };
1873
- }
1874
- }
1875
- async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
1876
- const empty = {
1877
- synced: false,
1878
- projectId: null,
1879
- downloaded: 0,
1880
- deleted: 0
1881
- };
1882
- const cfg = await tryReadApmConfig();
1883
- if (!cfg || !resolveApiKey(cfg)) {
1884
- console.log(
1885
- "[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
1886
- );
1887
- return empty;
1888
- }
1889
- const api = createApmApiClient(cfg);
1890
- const { projectId, diagnostic } = await resolveProjectIdForSync(
1891
- api,
1892
- workdirPath,
1893
- options
1894
- );
1895
- if (!projectId) {
1896
- console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
1897
- ${diagnostic ?? ""}`);
1898
- return empty;
1899
- }
1900
- const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
1901
- const docsDir = projectDocumentsDir(targetApmDir, projectId);
1902
- await ensureDirExists(docsDir);
1903
- const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
1904
- { projectId }
1905
- );
1906
- if (!remoteManifest) {
1907
- console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
1908
- return { ...empty, projectId };
1909
- }
1910
- const localManifest = readLocalManifest(targetApmDir, projectId);
1911
- const { download, deleteLocal } = diffManifestPaths(
1912
- remoteManifest,
1913
- localManifest
1914
- );
1915
- let downloaded = 0;
1916
- if (download.length > 0) {
1917
- const { list } = await api.cli.listProjectDocuments({
1918
- projectId,
1919
- paths: download.join(",")
1920
- });
1921
- for (const doc of list) {
1922
- const absPath = toFsPath(
1923
- projectDocumentLocalPath(targetApmDir, projectId, doc.path)
1924
- );
1925
- await ensureDirExists(dirname3(absPath));
1926
- writeFileSync4(absPath, doc.content, "utf8");
1927
- downloaded += 1;
1928
- }
1929
- }
1930
- let deleted = 0;
1931
- for (const path19 of deleteLocal) {
1932
- const absPath = toFsPath(
1933
- projectDocumentLocalPath(targetApmDir, projectId, path19)
1934
- );
1935
- if (existsSync3(absPath)) {
1936
- rmSync(absPath, { force: true });
1937
- deleted += 1;
1938
- }
1939
- }
1940
- writeFileSync4(
1941
- toFsPath(join5(docsDir, MANIFEST_FILE)),
1942
- `${JSON.stringify(remoteManifest, null, 2)}
1943
- `,
1944
- "utf8"
1945
- );
1946
- console.log(
1947
- `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
1948
- );
1949
- return {
1950
- synced: true,
1951
- projectId,
1952
- downloaded,
1953
- deleted
1954
- };
1955
- }
1956
- async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
1957
- const api = createApmApiClient(cfg);
1958
- const { projectId } = await resolveProjectIdForSync(
1959
- api,
1960
- workdirPath,
1961
- options
1962
- );
1963
- if (!projectId) {
1964
- return 0;
1965
- }
1966
- const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
1967
- const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
1968
- if (localPaths.length === 0) {
1969
- console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
1970
- return 0;
1971
- }
1972
- const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
1973
- const remoteHashByPath = new Map(
1974
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
1975
- );
1976
- const remoteDescriptionByPath = new Map(
1977
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
1978
- );
1979
- let synced = 0;
1980
- for (const path19 of localPaths) {
1981
- const absPath = toFsPath(
1982
- projectDocumentLocalPath(targetApmDir, projectId, path19)
1983
- );
1984
- const content = readFileSync3(absPath, "utf8");
1985
- const contentHash = hashLocalFileContent(content);
1986
- if (remoteHashByPath.get(path19) === contentHash) {
1987
- continue;
1988
- }
1989
- await api.cli.upsertProjectDocument({
1990
- projectId,
1991
- path: path19,
1992
- content,
1993
- description: remoteDescriptionByPath.get(path19) ?? void 0
1994
- });
1995
- synced += 1;
1996
- console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path19}`);
1997
- }
1998
- if (synced === 0) {
1999
- console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
2000
- }
2001
- return synced;
2002
- }
2003
-
2004
- // src/commands/init.ts
2005
- async function initializeWorkspace(workdir, options) {
2006
- await ensureWorkspaceApmDirForInit(workdir);
2007
- const apmDir = workspaceApmDir(workdir);
2008
- await copyTemplateFiles(apmDir, workdir);
2009
- const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
2010
- await syncProjectDocumentsPull(workdir, apmDir);
2011
- const trimmedName = options?.name?.trim();
2012
- if (trimmedName) {
2013
- const apmConfigPath = toFsPath(join6(apmDir, "apm.config.json"));
2014
- const config = readFileSync4(apmConfigPath, "utf8");
2015
- const configJson = JSON.parse(config);
2016
- configJson.name = trimmedName;
2017
- writeFileSync5(
2018
- apmConfigPath,
2019
- `${JSON.stringify(configJson, null, 2)}
2020
- `,
2021
- "utf8"
2022
- );
2023
- }
2024
- console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
2025
- return { didInit: true, syncResult };
2026
- }
2027
- async function ensureWorkspaceInitialized(workdir, options) {
2028
- if (isWorkspaceApmInitialized(workdir)) {
2029
- return { didInit: false };
2030
- }
2031
- console.log(
2032
- `[apm] \u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u672A\u68C0\u6D4B\u5230\u5DF2\u521D\u59CB\u5316\u7684 .apm\uFF0C\u6B63\u5728\u81EA\u52A8\u521D\u59CB\u5316\u2026`
2033
- );
2034
- return initializeWorkspace(workdir, options);
2035
- }
2036
- async function runInit(name) {
2037
- const workdir = resolveWorkdirPath();
2038
- const { syncResult } = await initializeWorkspace(workdir, { name });
2039
- console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
2040
- if (syncResult && !syncResult.synced) {
2041
- console.log(
2042
- "[apm] \u5F53\u524D .apm/apm.config.json \u4E3A\u6A21\u677F\u9ED8\u8BA4\u503C\uFF1B\u5B8C\u6210\u5E73\u53F0\u767B\u8BB0\u540E\u6267\u884C apm sync-deploy-config"
2043
- );
2044
- }
2045
- console.log("[apm] \u8BF7\u5728\u5E73\u53F0\u300C\u63A5\u5165\u7BA1\u7406 \u2192 \u5DE5\u4F5C\u7A7A\u95F4\u300D\u767B\u8BB0\u4E0A\u8FF0\u76EE\u5F55\u8DEF\u5F84");
2046
- }
2047
-
2048
- // src/commands/login.ts
2049
- init_config();
2050
- init_client();
2051
- import { existsSync as existsSync4 } from "fs";
2052
- import { ApiError } from "listpage-http";
1777
+ // src/commands/login.ts
2053
1778
  async function runLogin(opts) {
2054
1779
  const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
2055
1780
  const apiKey = (opts.apiKey?.trim() || process.env.APM_API_KEY?.trim() || "").replace(/^Bearer\s+/i, "");
@@ -2103,7 +1828,7 @@ async function runLogin(opts) {
2103
1828
  );
2104
1829
  const workdir = resolveWorkdirPath();
2105
1830
  const apmDir = workspaceApmDir(workdir);
2106
- if (existsSync4(apmDir)) {
1831
+ if (existsSync3(apmDir)) {
2107
1832
  await syncRemoteDeploymentConfig(workdir, apmDir);
2108
1833
  }
2109
1834
  }
@@ -2593,9 +2318,9 @@ function formatSessionMessagesXml(sessionId, messages) {
2593
2318
  }
2594
2319
 
2595
2320
  // src/commands/sync-session-attachments.ts
2596
- import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
2597
- import { join as join7 } from "path";
2598
- var MANIFEST_FILE2 = ".sync-manifest.json";
2321
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
2322
+ import { join as join6 } from "path";
2323
+ var MANIFEST_FILE = ".sync-manifest.json";
2599
2324
  async function downloadAttachment(cfg, attachmentId) {
2600
2325
  const base = cfg.baseUrl.trim().replace(/\/+$/, "");
2601
2326
  const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
@@ -2610,13 +2335,13 @@ async function downloadAttachment(cfg, attachmentId) {
2610
2335
  return Buffer.from(await res.arrayBuffer());
2611
2336
  }
2612
2337
  function loadManifest(dir) {
2613
- const path19 = join7(dir, MANIFEST_FILE2);
2614
- if (!existsSync5(path19)) {
2338
+ const path19 = join6(dir, MANIFEST_FILE);
2339
+ if (!existsSync4(path19)) {
2615
2340
  return { version: 1, attachments: {} };
2616
2341
  }
2617
2342
  try {
2618
2343
  const parsed = JSON.parse(
2619
- readFileSync5(path19, "utf8")
2344
+ readFileSync4(path19, "utf8")
2620
2345
  );
2621
2346
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
2622
2347
  return parsed;
@@ -2626,15 +2351,15 @@ function loadManifest(dir) {
2626
2351
  return { version: 1, attachments: {} };
2627
2352
  }
2628
2353
  function saveManifest(dir, manifest) {
2629
- writeFileSync6(
2630
- join7(dir, MANIFEST_FILE2),
2354
+ writeFileSync5(
2355
+ join6(dir, MANIFEST_FILE),
2631
2356
  `${JSON.stringify(manifest, null, 2)}
2632
2357
  `,
2633
2358
  "utf8"
2634
2359
  );
2635
2360
  }
2636
2361
  function isAttachmentUpToDate(entry, item, dest) {
2637
- if (!entry || !existsSync5(dest)) return false;
2362
+ if (!entry || !existsSync4(dest)) return false;
2638
2363
  if (entry.name !== item.name) return false;
2639
2364
  const createdAt = item.createdAt ?? "";
2640
2365
  return entry.createdAt === createdAt;
@@ -2649,7 +2374,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
2649
2374
  const nextManifest = { version: 1, attachments: {} };
2650
2375
  const names = [];
2651
2376
  for (const item of attachments) {
2652
- const dest = join7(dir, item.name);
2377
+ const dest = join6(dir, item.name);
2653
2378
  const entry = manifest.attachments[item.id];
2654
2379
  const createdAt = item.createdAt ?? "";
2655
2380
  names.push(item.name);
@@ -2659,7 +2384,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
2659
2384
  continue;
2660
2385
  }
2661
2386
  const buffer = await downloadAttachment(cfg, item.id);
2662
- writeFileSync6(dest, buffer);
2387
+ writeFileSync5(dest, buffer);
2663
2388
  nextManifest.attachments[item.id] = {
2664
2389
  name: item.name,
2665
2390
  createdAt
@@ -2670,7 +2395,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
2670
2395
  return names;
2671
2396
  }
2672
2397
  async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
2673
- const dir = join7(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
2398
+ const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
2674
2399
  return syncAttachmentsToDirectory(
2675
2400
  cfg,
2676
2401
  attachments,
@@ -2681,46 +2406,46 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
2681
2406
 
2682
2407
  // src/rules-sync.ts
2683
2408
  init_client();
2684
- import { basename as basename3, extname as extname2, join as join9 } from "path";
2685
- import { existsSync as existsSync7, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
2409
+ import { basename as basename3, extname as extname2, join as join8 } from "path";
2410
+ import { existsSync as existsSync6, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
2686
2411
 
2687
2412
  // src/skills-sync.ts
2688
2413
  import {
2689
2414
  copyFileSync as copyFileSync2,
2690
2415
  cpSync,
2691
- existsSync as existsSync6,
2416
+ existsSync as existsSync5,
2692
2417
  mkdirSync as mkdirSync4,
2693
- readdirSync as readdirSync4,
2694
- rmSync as rmSync2,
2418
+ readdirSync as readdirSync3,
2419
+ rmSync,
2695
2420
  statSync as statSync3,
2696
- writeFileSync as writeFileSync7
2421
+ writeFileSync as writeFileSync6
2697
2422
  } from "fs";
2698
- import { join as join8 } from "path";
2699
- var AGENTS_TEMPLATE_PATH = join8(CLI_TEMPLATE_DIR, "AGENTS.md");
2700
- var BASE_SKILLS_TEMPLATE_DIR = join8(CLI_TEMPLATE_DIR, "skills");
2701
- var BASE_RULES_TEMPLATE_DIR = join8(CLI_TEMPLATE_DIR, "rules");
2423
+ import { join as join7 } from "path";
2424
+ var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
2425
+ var BASE_SKILLS_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "skills");
2426
+ var BASE_RULES_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "rules");
2702
2427
  function sanitizeSkillDirName(name) {
2703
2428
  const trimmed = name.trim();
2704
2429
  if (!trimmed) return "skill";
2705
2430
  return trimmed.replace(/[/\\:*?"<>|]/g, "_");
2706
2431
  }
2707
2432
  function listBaseSkillDirNames() {
2708
- if (!existsSync6(BASE_SKILLS_TEMPLATE_DIR)) return [];
2709
- return readdirSync4(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
2710
- const path19 = join8(BASE_SKILLS_TEMPLATE_DIR, name);
2433
+ if (!existsSync5(BASE_SKILLS_TEMPLATE_DIR)) return [];
2434
+ return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
2435
+ const path19 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
2711
2436
  return statSync3(path19).isDirectory();
2712
2437
  });
2713
2438
  }
2714
2439
  function syncAgentsGuide(apmDir) {
2715
- if (!existsSync6(AGENTS_TEMPLATE_PATH)) return false;
2440
+ if (!existsSync5(AGENTS_TEMPLATE_PATH)) return false;
2716
2441
  mkdirSync4(apmDir, { recursive: true });
2717
- copyFileSync2(AGENTS_TEMPLATE_PATH, join8(apmDir, "AGENTS.md"));
2442
+ copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
2718
2443
  return true;
2719
2444
  }
2720
2445
  function listBaseRuleFileNames() {
2721
- if (!existsSync6(BASE_RULES_TEMPLATE_DIR)) return [];
2722
- return readdirSync4(BASE_RULES_TEMPLATE_DIR).filter((name) => {
2723
- const path19 = join8(BASE_RULES_TEMPLATE_DIR, name);
2446
+ if (!existsSync5(BASE_RULES_TEMPLATE_DIR)) return [];
2447
+ return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
2448
+ const path19 = join7(BASE_RULES_TEMPLATE_DIR, name);
2724
2449
  return statSync3(path19).isFile();
2725
2450
  });
2726
2451
  }
@@ -2728,8 +2453,8 @@ function syncBaseRules(rulesDir) {
2728
2453
  mkdirSync4(rulesDir, { recursive: true });
2729
2454
  const names = listBaseRuleFileNames();
2730
2455
  for (const name of names) {
2731
- const src = join8(BASE_RULES_TEMPLATE_DIR, name);
2732
- const dest = join8(rulesDir, name);
2456
+ const src = join7(BASE_RULES_TEMPLATE_DIR, name);
2457
+ const dest = join7(rulesDir, name);
2733
2458
  copyFileSync2(src, dest);
2734
2459
  }
2735
2460
  return names;
@@ -2738,8 +2463,8 @@ function syncBaseSkills(skillsDir) {
2738
2463
  mkdirSync4(skillsDir, { recursive: true });
2739
2464
  const names = listBaseSkillDirNames();
2740
2465
  for (const name of names) {
2741
- const src = join8(BASE_SKILLS_TEMPLATE_DIR, name);
2742
- const dest = join8(skillsDir, name);
2466
+ const src = join7(BASE_SKILLS_TEMPLATE_DIR, name);
2467
+ const dest = join7(skillsDir, name);
2743
2468
  cpSync(src, dest, { recursive: true, force: true });
2744
2469
  }
2745
2470
  return names;
@@ -2756,26 +2481,26 @@ function syncSupplementarySkills(skillsDir, list) {
2756
2481
  skipped.push(dirName);
2757
2482
  continue;
2758
2483
  }
2759
- const skillDir = join8(skillsDir, dirName);
2484
+ const skillDir = join7(skillsDir, dirName);
2760
2485
  mkdirSync4(skillDir, { recursive: true });
2761
- writeFileSync7(join8(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
2486
+ writeFileSync6(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
2762
2487
  written.push(dirName);
2763
2488
  }
2764
2489
  const removed = [];
2765
- if (!existsSync6(skillsDir)) return { written, skipped, removed };
2766
- for (const entry of readdirSync4(skillsDir)) {
2767
- const full = join8(skillsDir, entry);
2490
+ if (!existsSync5(skillsDir)) return { written, skipped, removed };
2491
+ for (const entry of readdirSync3(skillsDir)) {
2492
+ const full = join7(skillsDir, entry);
2768
2493
  if (!statSync3(full).isDirectory()) continue;
2769
2494
  if (baseNames.has(entry)) continue;
2770
2495
  if (apiDirNames.has(entry)) continue;
2771
- rmSync2(full, { recursive: true, force: true });
2496
+ rmSync(full, { recursive: true, force: true });
2772
2497
  removed.push(entry);
2773
2498
  }
2774
2499
  return { written, skipped, removed };
2775
2500
  }
2776
2501
 
2777
2502
  // src/rules-sync.ts
2778
- var MANIFEST_FILE3 = ".rules-sync-manifest.json";
2503
+ var MANIFEST_FILE2 = ".rules-sync-manifest.json";
2779
2504
  function ruleLocalFileName(ruleName) {
2780
2505
  const trimmed = ruleName.trim();
2781
2506
  if (!trimmed) return "rule.md";
@@ -2784,13 +2509,13 @@ function ruleLocalFileName(ruleName) {
2784
2509
  return `${sanitized}.md`;
2785
2510
  }
2786
2511
  function loadManifest2(rulesDir) {
2787
- const path19 = join9(rulesDir, MANIFEST_FILE3);
2788
- if (!existsSync7(toFsPath(path19))) {
2512
+ const path19 = join8(rulesDir, MANIFEST_FILE2);
2513
+ if (!existsSync6(toFsPath(path19))) {
2789
2514
  return { version: 1, rules: {} };
2790
2515
  }
2791
2516
  try {
2792
2517
  const parsed = JSON.parse(
2793
- readFileSync6(toFsPath(path19), "utf8")
2518
+ readFileSync5(toFsPath(path19), "utf8")
2794
2519
  );
2795
2520
  if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
2796
2521
  return parsed;
@@ -2800,8 +2525,8 @@ function loadManifest2(rulesDir) {
2800
2525
  return { version: 1, rules: {} };
2801
2526
  }
2802
2527
  function saveManifest2(rulesDir, manifest) {
2803
- writeFileSync8(
2804
- toFsPath(join9(rulesDir, MANIFEST_FILE3)),
2528
+ writeFileSync7(
2529
+ toFsPath(join8(rulesDir, MANIFEST_FILE2)),
2805
2530
  `${JSON.stringify(manifest, null, 2)}
2806
2531
  `,
2807
2532
  "utf8"
@@ -2811,18 +2536,18 @@ function isBaseRuleFileName(fileName) {
2811
2536
  return listBaseRuleFileNames().includes(basename3(fileName));
2812
2537
  }
2813
2538
  function isRuleUpToDate(entry, rule, dest) {
2814
- if (!entry || !existsSync7(toFsPath(dest))) return false;
2539
+ if (!entry || !existsSync6(toFsPath(dest))) return false;
2815
2540
  if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
2816
2541
  const updatedAt = rule.updatedAt ?? "";
2817
2542
  if (entry.updatedAt !== updatedAt) return false;
2818
- const localContent = readFileSync6(toFsPath(dest), "utf8");
2543
+ const localContent = readFileSync5(toFsPath(dest), "utf8");
2819
2544
  return localContent === (rule.content ?? "");
2820
2545
  }
2821
2546
  async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
2822
2547
  const api = createApmApiClient(cfg);
2823
2548
  const baseline = await resolveBranchBaseline(api, sessionId, workdirPath);
2824
2549
  const repositoryId = baseline.repositoryId;
2825
- const rulesDir = join9(apmRoot ?? workspaceApmDir(workdirPath), "rules");
2550
+ const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
2826
2551
  await ensureDirExists(rulesDir);
2827
2552
  if (!repositoryId) {
2828
2553
  console.log(
@@ -2839,7 +2564,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
2839
2564
  for (const rule of list) {
2840
2565
  remoteIds.add(rule.id);
2841
2566
  const fileName = ruleLocalFileName(rule.name);
2842
- const dest = join9(rulesDir, fileName);
2567
+ const dest = join8(rulesDir, fileName);
2843
2568
  const entry = manifest.rules[rule.id];
2844
2569
  const updatedAt = rule.updatedAt ?? "";
2845
2570
  if (isRuleUpToDate(entry, rule, dest)) {
@@ -2848,7 +2573,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
2848
2573
  console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
2849
2574
  continue;
2850
2575
  }
2851
- writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
2576
+ writeFileSync7(toFsPath(dest), rule.content ?? "", "utf8");
2852
2577
  nextManifest.rules[rule.id] = { fileName, updatedAt };
2853
2578
  written.push(fileName);
2854
2579
  console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
@@ -2857,9 +2582,9 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
2857
2582
  for (const [ruleId, entry] of Object.entries(manifest.rules)) {
2858
2583
  if (remoteIds.has(ruleId)) continue;
2859
2584
  if (isBaseRuleFileName(entry.fileName)) continue;
2860
- const dest = join9(rulesDir, entry.fileName);
2861
- if (existsSync7(toFsPath(dest))) {
2862
- rmSync3(toFsPath(dest), { force: true });
2585
+ const dest = join8(rulesDir, entry.fileName);
2586
+ if (existsSync6(toFsPath(dest))) {
2587
+ rmSync2(toFsPath(dest), { force: true });
2863
2588
  }
2864
2589
  removed.push(entry.fileName);
2865
2590
  console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
@@ -2868,6 +2593,275 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
2868
2593
  return { written, skipped, removed, repositoryId };
2869
2594
  }
2870
2595
 
2596
+ // src/project-documents-sync.ts
2597
+ init_client();
2598
+ init_config();
2599
+ import {
2600
+ existsSync as existsSync7,
2601
+ readdirSync as readdirSync4,
2602
+ readFileSync as readFileSync6,
2603
+ rmSync as rmSync3,
2604
+ writeFileSync as writeFileSync8
2605
+ } from "fs";
2606
+ import { createHash } from "crypto";
2607
+ import { dirname as dirname3, join as join9, relative as relative2, sep } from "path";
2608
+ var MANIFEST_FILE3 = "manifest.json";
2609
+ function normalizeProjectIdForPath(projectId) {
2610
+ const id = projectId.trim();
2611
+ if (!id) {
2612
+ throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
2613
+ }
2614
+ if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
2615
+ throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
2616
+ }
2617
+ return id;
2618
+ }
2619
+ function projectDocumentsDir(apmRoot, projectId) {
2620
+ const id = normalizeProjectIdForPath(projectId);
2621
+ return join9(apmRoot ?? workspaceApmDir(), "project", id);
2622
+ }
2623
+ function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
2624
+ const normalized = normalizeLocalDocumentPath(documentPath);
2625
+ return join9(
2626
+ projectDocumentsDir(apmRoot, projectId),
2627
+ ...normalized.split("/")
2628
+ );
2629
+ }
2630
+ function normalizeLocalDocumentPath(path19) {
2631
+ const trimmed = path19.trim().replace(/\\/g, "/");
2632
+ if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
2633
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
2634
+ }
2635
+ const segments = trimmed.split("/").filter(Boolean);
2636
+ if (segments.some((segment) => segment === ".." || segment === ".")) {
2637
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
2638
+ }
2639
+ return segments.join("/");
2640
+ }
2641
+ function hashLocalFileContent(content) {
2642
+ return createHash("sha256").update(content, "utf8").digest("hex");
2643
+ }
2644
+ function readLocalManifest(apmRoot, projectId) {
2645
+ const manifestPath3 = join9(
2646
+ projectDocumentsDir(apmRoot, projectId),
2647
+ MANIFEST_FILE3
2648
+ );
2649
+ if (!existsSync7(manifestPath3)) {
2650
+ return null;
2651
+ }
2652
+ try {
2653
+ return JSON.parse(
2654
+ readFileSync6(manifestPath3, "utf8")
2655
+ );
2656
+ } catch {
2657
+ return null;
2658
+ }
2659
+ }
2660
+ function listLocalDocumentPaths(apmRoot, projectId) {
2661
+ const root = projectDocumentsDir(apmRoot, projectId);
2662
+ if (!existsSync7(root)) {
2663
+ return [];
2664
+ }
2665
+ const paths = [];
2666
+ const walk = (dir) => {
2667
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
2668
+ const abs = join9(dir, entry.name);
2669
+ if (entry.isDirectory()) {
2670
+ walk(abs);
2671
+ continue;
2672
+ }
2673
+ if (entry.isFile() && entry.name === MANIFEST_FILE3) {
2674
+ continue;
2675
+ }
2676
+ const rel = relative2(root, abs).split(sep).join("/");
2677
+ paths.push(rel);
2678
+ }
2679
+ };
2680
+ walk(root);
2681
+ return paths.sort();
2682
+ }
2683
+ function diffManifestPaths(remote, local) {
2684
+ const remoteMap = new Map(
2685
+ (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
2686
+ );
2687
+ const localMap = new Map(
2688
+ (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
2689
+ );
2690
+ const download = [];
2691
+ for (const [path19, hash] of remoteMap) {
2692
+ if (localMap.get(path19) !== hash) {
2693
+ download.push(path19);
2694
+ }
2695
+ }
2696
+ const deleteLocal = [];
2697
+ for (const path19 of localMap.keys()) {
2698
+ if (!remoteMap.has(path19)) {
2699
+ deleteLocal.push(path19);
2700
+ }
2701
+ }
2702
+ return { download, deleteLocal };
2703
+ }
2704
+ async function resolveProjectIdForSync(api, workdirPath, options) {
2705
+ const explicit = options?.projectId?.trim();
2706
+ if (explicit) {
2707
+ return { projectId: explicit, diagnostic: null };
2708
+ }
2709
+ try {
2710
+ const baseline = await api.cli.workspaceBaseline({ workdirPath });
2711
+ const projectId = baseline.projectId?.trim() || null;
2712
+ if (projectId) {
2713
+ return { projectId, diagnostic: null };
2714
+ }
2715
+ const projectIds = baseline.projectIds ?? [];
2716
+ if (projectIds.length === 0) {
2717
+ return {
2718
+ projectId: null,
2719
+ diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
2720
+ \u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
2721
+ };
2722
+ }
2723
+ return {
2724
+ projectId: null,
2725
+ diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
2726
+ \u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
2727
+ };
2728
+ } catch (err) {
2729
+ const detail = err instanceof Error ? err.message : String(err);
2730
+ return {
2731
+ projectId: null,
2732
+ diagnostic: detail.replace(/^\[apm\]\s*/, "")
2733
+ };
2734
+ }
2735
+ }
2736
+ async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
2737
+ const empty = {
2738
+ synced: false,
2739
+ projectId: null,
2740
+ downloaded: 0,
2741
+ deleted: 0
2742
+ };
2743
+ const cfg = await tryReadApmConfig();
2744
+ if (!cfg || !resolveApiKey(cfg)) {
2745
+ console.log(
2746
+ "[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
2747
+ );
2748
+ return empty;
2749
+ }
2750
+ const api = createApmApiClient(cfg);
2751
+ const { projectId, diagnostic } = await resolveProjectIdForSync(
2752
+ api,
2753
+ workdirPath,
2754
+ options
2755
+ );
2756
+ if (!projectId) {
2757
+ console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
2758
+ ${diagnostic ?? ""}`);
2759
+ return empty;
2760
+ }
2761
+ const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
2762
+ const docsDir = projectDocumentsDir(targetApmDir, projectId);
2763
+ await ensureDirExists(docsDir);
2764
+ const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
2765
+ { projectId }
2766
+ );
2767
+ if (!remoteManifest) {
2768
+ console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
2769
+ return { ...empty, projectId };
2770
+ }
2771
+ const localManifest = readLocalManifest(targetApmDir, projectId);
2772
+ const { download, deleteLocal } = diffManifestPaths(
2773
+ remoteManifest,
2774
+ localManifest
2775
+ );
2776
+ let downloaded = 0;
2777
+ if (download.length > 0) {
2778
+ const { list } = await api.cli.listProjectDocuments({
2779
+ projectId,
2780
+ paths: download.join(",")
2781
+ });
2782
+ for (const doc of list) {
2783
+ const absPath = toFsPath(
2784
+ projectDocumentLocalPath(targetApmDir, projectId, doc.path)
2785
+ );
2786
+ await ensureDirExists(dirname3(absPath));
2787
+ writeFileSync8(absPath, doc.content, "utf8");
2788
+ downloaded += 1;
2789
+ }
2790
+ }
2791
+ let deleted = 0;
2792
+ for (const path19 of deleteLocal) {
2793
+ const absPath = toFsPath(
2794
+ projectDocumentLocalPath(targetApmDir, projectId, path19)
2795
+ );
2796
+ if (existsSync7(absPath)) {
2797
+ rmSync3(absPath, { force: true });
2798
+ deleted += 1;
2799
+ }
2800
+ }
2801
+ writeFileSync8(
2802
+ toFsPath(join9(docsDir, MANIFEST_FILE3)),
2803
+ `${JSON.stringify(remoteManifest, null, 2)}
2804
+ `,
2805
+ "utf8"
2806
+ );
2807
+ console.log(
2808
+ `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
2809
+ );
2810
+ return {
2811
+ synced: true,
2812
+ projectId,
2813
+ downloaded,
2814
+ deleted
2815
+ };
2816
+ }
2817
+ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
2818
+ const api = createApmApiClient(cfg);
2819
+ const { projectId } = await resolveProjectIdForSync(
2820
+ api,
2821
+ workdirPath,
2822
+ options
2823
+ );
2824
+ if (!projectId) {
2825
+ return 0;
2826
+ }
2827
+ const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
2828
+ const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
2829
+ if (localPaths.length === 0) {
2830
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
2831
+ return 0;
2832
+ }
2833
+ const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
2834
+ const remoteHashByPath = new Map(
2835
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
2836
+ );
2837
+ const remoteDescriptionByPath = new Map(
2838
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
2839
+ );
2840
+ let synced = 0;
2841
+ for (const path19 of localPaths) {
2842
+ const absPath = toFsPath(
2843
+ projectDocumentLocalPath(targetApmDir, projectId, path19)
2844
+ );
2845
+ const content = readFileSync6(absPath, "utf8");
2846
+ const contentHash = hashLocalFileContent(content);
2847
+ if (remoteHashByPath.get(path19) === contentHash) {
2848
+ continue;
2849
+ }
2850
+ await api.cli.upsertProjectDocument({
2851
+ projectId,
2852
+ path: path19,
2853
+ content,
2854
+ description: remoteDescriptionByPath.get(path19) ?? void 0
2855
+ });
2856
+ synced += 1;
2857
+ console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path19}`);
2858
+ }
2859
+ if (synced === 0) {
2860
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
2861
+ }
2862
+ return synced;
2863
+ }
2864
+
2871
2865
  // src/commands/pull.ts
2872
2866
  async function runPull(sessionId, remoteWorkdir) {
2873
2867
  const trimmedId = sessionId.trim();
@@ -10557,9 +10551,7 @@ function buildProgram() {
10557
10551
  ).option("--api-key <key>", "\u5BA2\u6237\u673A API Key\uFF08cm_ \u5F00\u5934\uFF09").option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u4F8B\u5982 http://127.0.0.1:3000").action(async (opts) => {
10558
10552
  await runLogin(opts);
10559
10553
  });
10560
- program.command("init").description(
10561
- "\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521B\u5EFA .apm \u5E76\u521D\u59CB\u5316\u6A21\u677F\uFF1B\u5DF2\u767B\u5F55\u4E14\u5E73\u53F0\u6709\u5339\u914D\u90E8\u7F72\u914D\u7F6E\u65F6\u4F1A\u540C\u6B65 apm.config.json"
10562
- ).option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
10554
+ program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521B\u5EFA .apm \u5E76\u521D\u59CB\u5316\u6A21\u677F").option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
10563
10555
  await runInit(opts.name);
10564
10556
  });
10565
10557
  program.command("update").description(