ai-project-manage-cli 7.1.18 → 7.1.20

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
@@ -1099,8 +1099,8 @@ import { Command } from "commander";
1099
1099
  init_config();
1100
1100
 
1101
1101
  // src/commands/init.ts
1102
- import { join as join5 } from "path";
1103
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
1102
+ import { join as join6 } from "path";
1103
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
1104
1104
 
1105
1105
  // src/command-utils.ts
1106
1106
  init_config();
@@ -1388,8 +1388,8 @@ async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
1388
1388
 
1389
1389
  // src/deployment-config-sync.ts
1390
1390
  init_client();
1391
- import { join as join3 } from "path";
1392
- import { writeFileSync as writeFileSync3 } from "fs";
1391
+ import { join as join4 } from "path";
1392
+ import { writeFileSync as writeFileSync4 } from "fs";
1393
1393
 
1394
1394
  // src/git-remote.ts
1395
1395
  import { execFile } from "child_process";
@@ -1649,6 +1649,162 @@ async function resolveBranchBaseline(api, sessionId, workdirPath) {
1649
1649
 
1650
1650
  // src/deployment-config-sync.ts
1651
1651
  init_config();
1652
+
1653
+ // src/workspace-repos.ts
1654
+ import {
1655
+ existsSync as existsSync2,
1656
+ mkdirSync as mkdirSync3,
1657
+ readFileSync as readFileSync3,
1658
+ readdirSync as readdirSync2,
1659
+ statSync as statSync2,
1660
+ writeFileSync as writeFileSync3
1661
+ } from "fs";
1662
+ import { basename as basename2, dirname as dirname2, join as join3, relative, resolve as resolve3 } from "path";
1663
+ var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
1664
+ var WORKSPACE_REPOS_VERSION = 1;
1665
+ function manifestPath(workdir) {
1666
+ return join3(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
1667
+ }
1668
+ function absoluteRepoPath(workdir, entry) {
1669
+ if (entry.path === "." || entry.path === "") {
1670
+ return workdir;
1671
+ }
1672
+ return resolve3(workdir, entry.path);
1673
+ }
1674
+ function normalizeRepoEntry(raw) {
1675
+ if (!raw || typeof raw !== "object") return null;
1676
+ const o = raw;
1677
+ if (typeof o.path !== "string" || !o.path.trim()) return null;
1678
+ const entry = {
1679
+ path: o.path.trim().replace(/\\/g, "/")
1680
+ };
1681
+ if (typeof o.remoteUrl === "string" && o.remoteUrl.trim()) {
1682
+ entry.remoteUrl = o.remoteUrl.trim();
1683
+ } else if (o.remoteUrl === null) {
1684
+ entry.remoteUrl = null;
1685
+ }
1686
+ return entry;
1687
+ }
1688
+ function readManifest(workdir) {
1689
+ const path19 = toFsPath(manifestPath(workdir));
1690
+ if (!existsSync2(path19)) {
1691
+ return null;
1692
+ }
1693
+ try {
1694
+ const raw = JSON.parse(
1695
+ readFileSync3(path19, "utf8")
1696
+ );
1697
+ if (raw?.version !== WORKSPACE_REPOS_VERSION) {
1698
+ return null;
1699
+ }
1700
+ if (raw.kind !== "single" && raw.kind !== "multi") {
1701
+ return null;
1702
+ }
1703
+ if (!Array.isArray(raw.repos) || raw.repos.length === 0) {
1704
+ return null;
1705
+ }
1706
+ if (typeof raw.workdir !== "string" || !raw.workdir.trim()) {
1707
+ return null;
1708
+ }
1709
+ const repos = raw.repos.map((item) => normalizeRepoEntry(item)).filter((item) => item != null);
1710
+ if (repos.length === 0) {
1711
+ return null;
1712
+ }
1713
+ return {
1714
+ version: WORKSPACE_REPOS_VERSION,
1715
+ kind: raw.kind,
1716
+ workdir: raw.workdir,
1717
+ repos,
1718
+ scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt.trim() ? raw.scannedAt : (/* @__PURE__ */ new Date()).toISOString()
1719
+ };
1720
+ } catch {
1721
+ return null;
1722
+ }
1723
+ }
1724
+ function writeManifest(workdir, manifest) {
1725
+ const apmDir = toFsPath(workspaceApmDir(workdir));
1726
+ mkdirSync3(apmDir, { recursive: true });
1727
+ const path19 = toFsPath(manifestPath(workdir));
1728
+ writeFileSync3(path19, `${JSON.stringify(manifest, null, 2)}
1729
+ `, "utf8");
1730
+ }
1731
+ function isPathInsideOrEqual(parentAbs, childAbs) {
1732
+ const parent = normalizeWorkdirPath(parentAbs);
1733
+ const child = normalizeWorkdirPath(childAbs);
1734
+ if (child === parent) return true;
1735
+ const prefix = parent.endsWith("/") ? parent : `${parent}/`;
1736
+ return child.startsWith(prefix);
1737
+ }
1738
+ function findWorkspaceReposManifestNearPath(startDirInput) {
1739
+ let current = resolve3(startDirInput);
1740
+ for (; ; ) {
1741
+ const candidates = /* @__PURE__ */ new Set([normalizeWorkdirPath(current)]);
1742
+ try {
1743
+ if (existsSync2(toFsPath(current))) {
1744
+ candidates.add(resolveWorkdirPath(current));
1745
+ }
1746
+ } catch {
1747
+ }
1748
+ for (const candidate of candidates) {
1749
+ const cached = readManifest(candidate);
1750
+ if (!cached) continue;
1751
+ const cachedWorkdir = resolveWorkdirPath(cached.workdir);
1752
+ const candidateNorm = resolveWorkdirPath(candidate);
1753
+ if (cachedWorkdir === candidateNorm) {
1754
+ return { ...cached, workdir: cachedWorkdir };
1755
+ }
1756
+ }
1757
+ const parent = dirname2(current);
1758
+ if (parent === current) {
1759
+ return null;
1760
+ }
1761
+ current = parent;
1762
+ }
1763
+ }
1764
+ function matchWorkspaceRepoEntryForPath(manifest, pathInput) {
1765
+ const target = resolveWorkdirPath(pathInput);
1766
+ const workdir = resolveWorkdirPath(manifest.workdir);
1767
+ let best = null;
1768
+ let bestLen = -1;
1769
+ for (const entry of manifest.repos) {
1770
+ const abs = resolveWorkdirPath(absoluteRepoPath(workdir, entry));
1771
+ if (!isPathInsideOrEqual(abs, target)) {
1772
+ continue;
1773
+ }
1774
+ if (abs.length > bestLen) {
1775
+ best = entry;
1776
+ bestLen = abs.length;
1777
+ }
1778
+ }
1779
+ return best;
1780
+ }
1781
+ async function enrichWorkspaceReposRemoteUrls(manifest) {
1782
+ let changed = false;
1783
+ const repos = [];
1784
+ for (const entry of manifest.repos) {
1785
+ const abs = absoluteRepoPath(manifest.workdir, entry);
1786
+ const remoteUrl = await tryReadHttpsGitOriginUrl(abs);
1787
+ const next = {
1788
+ path: entry.path,
1789
+ remoteUrl: remoteUrl || null
1790
+ };
1791
+ if ((entry.remoteUrl ?? null) !== next.remoteUrl) {
1792
+ changed = true;
1793
+ }
1794
+ repos.push(next);
1795
+ }
1796
+ const nextManifest = {
1797
+ ...manifest,
1798
+ repos,
1799
+ scannedAt: changed ? (/* @__PURE__ */ new Date()).toISOString() : manifest.scannedAt
1800
+ };
1801
+ if (changed) {
1802
+ writeManifest(manifest.workdir, nextManifest);
1803
+ }
1804
+ return nextManifest;
1805
+ }
1806
+
1807
+ // src/deployment-config-sync.ts
1652
1808
  var TEMPLATE_HINT = "\u4FDD\u7559\u6A21\u677F .apm/apm.config.json";
1653
1809
  var SYNC_HINT = "\u767B\u8BB0\u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u3001\u7ED1\u5B9A\u4ED3\u5E93\u540E\uFF0C\u53EF\u6267\u884C: apm sync-deploy-config";
1654
1810
  async function resolveRepositoryIdForSync(api, workdirPath) {
@@ -1663,7 +1819,73 @@ async function resolveRepositoryIdForSync(api, workdirPath) {
1663
1819
  };
1664
1820
  }
1665
1821
  }
1666
- async function syncRemoteDeploymentConfig(workdirPath, apmDir) {
1822
+ async function resolveDeployCwdForRepository(workspaceRoot, repositoryId, api) {
1823
+ const start = resolveWorkdirPath(workspaceRoot);
1824
+ let manifest = findWorkspaceReposManifestNearPath(start);
1825
+ if (!manifest) {
1826
+ return null;
1827
+ }
1828
+ try {
1829
+ manifest = await enrichWorkspaceReposRemoteUrls(manifest);
1830
+ } catch {
1831
+ return null;
1832
+ }
1833
+ let client = api;
1834
+ if (!client) {
1835
+ const cfg = await tryReadApmConfig();
1836
+ if (!cfg || !resolveApiKey(cfg)) {
1837
+ return null;
1838
+ }
1839
+ client = createApmApiClient(cfg);
1840
+ }
1841
+ for (const entry of manifest.repos) {
1842
+ const abs = absoluteRepoPath(manifest.workdir, entry);
1843
+ const remoteUrl = toHttpsGitRemoteUrl(entry.remoteUrl ?? "");
1844
+ if (!remoteUrl) {
1845
+ continue;
1846
+ }
1847
+ let baseBranch = "";
1848
+ try {
1849
+ const gitRoot = await resolveGitRepoRoot(abs);
1850
+ baseBranch = (await resolveDefaultRemoteBranch(gitRoot)).trim();
1851
+ } catch {
1852
+ continue;
1853
+ }
1854
+ if (!baseBranch) {
1855
+ continue;
1856
+ }
1857
+ try {
1858
+ const matched = await client.cli.matchRepository({
1859
+ url: remoteUrl,
1860
+ baseBranch
1861
+ });
1862
+ if (matched.repositoryId?.trim() === repositoryId) {
1863
+ return abs;
1864
+ }
1865
+ } catch {
1866
+ continue;
1867
+ }
1868
+ }
1869
+ return null;
1870
+ }
1871
+ async function writeDeploymentConfigContent(apmDir, content, configName) {
1872
+ let parsed;
1873
+ try {
1874
+ parsed = JSON.parse(content);
1875
+ } catch {
1876
+ console.warn(
1877
+ `[apm] \u8FDC\u7A0B\u90E8\u7F72\u914D\u7F6E\u300C${configName}\u300DJSON \u65E0\u6548\uFF08${TEMPLATE_HINT}\uFF09`
1878
+ );
1879
+ return false;
1880
+ }
1881
+ const apmConfigPath = toFsPath(join4(apmDir, "apm.config.json"));
1882
+ writeFileSync4(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
1883
+ `, "utf8");
1884
+ console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${configName}`);
1885
+ console.log("[apm] \u5DF2\u5199\u5165 .apm/apm.config.json");
1886
+ return true;
1887
+ }
1888
+ async function syncRemoteDeploymentConfig(workdirPath, apmDir, options) {
1667
1889
  const cfg = await tryReadApmConfig();
1668
1890
  if (!cfg || !resolveApiKey(cfg)) {
1669
1891
  console.log(
@@ -1673,10 +1895,14 @@ async function syncRemoteDeploymentConfig(workdirPath, apmDir) {
1673
1895
  return { synced: false, repositoryId: null };
1674
1896
  }
1675
1897
  const api = createApmApiClient(cfg);
1676
- const { repositoryId, diagnostic } = await resolveRepositoryIdForSync(
1677
- api,
1678
- workdirPath
1679
- );
1898
+ const preferredId = options?.repositoryId?.trim() || "";
1899
+ let repositoryId = preferredId || null;
1900
+ let diagnostic = null;
1901
+ if (!repositoryId) {
1902
+ const resolved = await resolveRepositoryIdForSync(api, workdirPath);
1903
+ repositoryId = resolved.repositoryId;
1904
+ diagnostic = resolved.diagnostic;
1905
+ }
1680
1906
  if (!repositoryId) {
1681
1907
  console.log(
1682
1908
  `[apm] \u672A\u80FD\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E\uFF08${TEMPLATE_HINT}\uFF09\u3002
@@ -1693,21 +1919,15 @@ ${diagnostic ?? ""}
1693
1919
  );
1694
1920
  return { synced: false, repositoryId };
1695
1921
  }
1696
- let parsed;
1697
- try {
1698
- parsed = JSON.parse(config.content);
1699
- } catch {
1700
- console.warn(
1701
- `[apm] \u8FDC\u7A0B\u90E8\u7F72\u914D\u7F6E\u300C${config.name}\u300DJSON \u65E0\u6548\uFF08${TEMPLATE_HINT}\uFF09`
1702
- );
1922
+ const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
1923
+ const wrote = await writeDeploymentConfigContent(
1924
+ targetApmDir,
1925
+ config.content,
1926
+ config.name
1927
+ );
1928
+ if (!wrote) {
1703
1929
  return { synced: false, repositoryId };
1704
1930
  }
1705
- const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
1706
- const apmConfigPath = toFsPath(join3(targetApmDir, "apm.config.json"));
1707
- writeFileSync3(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
1708
- `, "utf8");
1709
- console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${config.name}`);
1710
- console.log("[apm] \u5DF2\u5199\u5165 .apm/apm.config.json");
1711
1931
  return { synced: true, repositoryId, configName: config.name };
1712
1932
  }
1713
1933
 
@@ -1715,14 +1935,14 @@ ${diagnostic ?? ""}
1715
1935
  init_client();
1716
1936
  init_config();
1717
1937
  import {
1718
- existsSync as existsSync2,
1719
- readdirSync as readdirSync2,
1720
- readFileSync as readFileSync3,
1938
+ existsSync as existsSync3,
1939
+ readdirSync as readdirSync3,
1940
+ readFileSync as readFileSync4,
1721
1941
  rmSync,
1722
- writeFileSync as writeFileSync4
1942
+ writeFileSync as writeFileSync5
1723
1943
  } from "fs";
1724
1944
  import { createHash } from "crypto";
1725
- import { dirname as dirname2, join as join4, relative, sep } from "path";
1945
+ import { dirname as dirname3, join as join5, relative as relative2, sep } from "path";
1726
1946
  var MANIFEST_FILE = "manifest.json";
1727
1947
  function normalizeProjectIdForPath(projectId) {
1728
1948
  const id = projectId.trim();
@@ -1736,11 +1956,11 @@ function normalizeProjectIdForPath(projectId) {
1736
1956
  }
1737
1957
  function projectDocumentsDir(apmRoot, projectId) {
1738
1958
  const id = normalizeProjectIdForPath(projectId);
1739
- return join4(apmRoot ?? workspaceApmDir(), "project", id);
1959
+ return join5(apmRoot ?? workspaceApmDir(), "project", id);
1740
1960
  }
1741
1961
  function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
1742
1962
  const normalized = normalizeLocalDocumentPath(documentPath);
1743
- return join4(
1963
+ return join5(
1744
1964
  projectDocumentsDir(apmRoot, projectId),
1745
1965
  ...normalized.split("/")
1746
1966
  );
@@ -1760,16 +1980,16 @@ function hashLocalFileContent(content) {
1760
1980
  return createHash("sha256").update(content, "utf8").digest("hex");
1761
1981
  }
1762
1982
  function readLocalManifest(apmRoot, projectId) {
1763
- const manifestPath3 = join4(
1983
+ const manifestPath3 = join5(
1764
1984
  projectDocumentsDir(apmRoot, projectId),
1765
1985
  MANIFEST_FILE
1766
1986
  );
1767
- if (!existsSync2(manifestPath3)) {
1987
+ if (!existsSync3(manifestPath3)) {
1768
1988
  return null;
1769
1989
  }
1770
1990
  try {
1771
1991
  return JSON.parse(
1772
- readFileSync3(manifestPath3, "utf8")
1992
+ readFileSync4(manifestPath3, "utf8")
1773
1993
  );
1774
1994
  } catch {
1775
1995
  return null;
@@ -1777,13 +1997,13 @@ function readLocalManifest(apmRoot, projectId) {
1777
1997
  }
1778
1998
  function listLocalDocumentPaths(apmRoot, projectId) {
1779
1999
  const root = projectDocumentsDir(apmRoot, projectId);
1780
- if (!existsSync2(root)) {
2000
+ if (!existsSync3(root)) {
1781
2001
  return [];
1782
2002
  }
1783
2003
  const paths = [];
1784
2004
  const walk = (dir) => {
1785
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
1786
- const abs = join4(dir, entry.name);
2005
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
2006
+ const abs = join5(dir, entry.name);
1787
2007
  if (entry.isDirectory()) {
1788
2008
  walk(abs);
1789
2009
  continue;
@@ -1791,7 +2011,7 @@ function listLocalDocumentPaths(apmRoot, projectId) {
1791
2011
  if (entry.isFile() && entry.name === MANIFEST_FILE) {
1792
2012
  continue;
1793
2013
  }
1794
- const rel = relative(root, abs).split(sep).join("/");
2014
+ const rel = relative2(root, abs).split(sep).join("/");
1795
2015
  paths.push(rel);
1796
2016
  }
1797
2017
  };
@@ -1901,8 +2121,8 @@ ${diagnostic ?? ""}`);
1901
2121
  const absPath = toFsPath(
1902
2122
  projectDocumentLocalPath(targetApmDir, projectId, doc.path)
1903
2123
  );
1904
- await ensureDirExists(dirname2(absPath));
1905
- writeFileSync4(absPath, doc.content, "utf8");
2124
+ await ensureDirExists(dirname3(absPath));
2125
+ writeFileSync5(absPath, doc.content, "utf8");
1906
2126
  downloaded += 1;
1907
2127
  }
1908
2128
  }
@@ -1911,13 +2131,13 @@ ${diagnostic ?? ""}`);
1911
2131
  const absPath = toFsPath(
1912
2132
  projectDocumentLocalPath(targetApmDir, projectId, path19)
1913
2133
  );
1914
- if (existsSync2(absPath)) {
2134
+ if (existsSync3(absPath)) {
1915
2135
  rmSync(absPath, { force: true });
1916
2136
  deleted += 1;
1917
2137
  }
1918
2138
  }
1919
- writeFileSync4(
1920
- toFsPath(join4(docsDir, MANIFEST_FILE)),
2139
+ writeFileSync5(
2140
+ toFsPath(join5(docsDir, MANIFEST_FILE)),
1921
2141
  `${JSON.stringify(remoteManifest, null, 2)}
1922
2142
  `,
1923
2143
  "utf8"
@@ -1960,7 +2180,7 @@ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
1960
2180
  const absPath = toFsPath(
1961
2181
  projectDocumentLocalPath(targetApmDir, projectId, path19)
1962
2182
  );
1963
- const content = readFileSync3(absPath, "utf8");
2183
+ const content = readFileSync4(absPath, "utf8");
1964
2184
  const contentHash = hashLocalFileContent(content);
1965
2185
  if (remoteHashByPath.get(path19) === contentHash) {
1966
2186
  continue;
@@ -1999,11 +2219,11 @@ async function ensureWorkspaceInitialized(workdir, options) {
1999
2219
  await syncProjectDocumentsPull(workdir, apmDir);
2000
2220
  const trimmedName = options?.name?.trim();
2001
2221
  if (trimmedName) {
2002
- const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
2003
- const config = readFileSync4(apmConfigPath, "utf8");
2222
+ const apmConfigPath = toFsPath(join6(apmDir, "apm.config.json"));
2223
+ const config = readFileSync5(apmConfigPath, "utf8");
2004
2224
  const configJson = JSON.parse(config);
2005
2225
  configJson.name = trimmedName;
2006
- writeFileSync5(
2226
+ writeFileSync6(
2007
2227
  apmConfigPath,
2008
2228
  `${JSON.stringify(configJson, null, 2)}
2009
2229
  `,
@@ -2036,7 +2256,7 @@ async function runInit(name) {
2036
2256
  // src/commands/login.ts
2037
2257
  init_config();
2038
2258
  init_client();
2039
- import { existsSync as existsSync3 } from "fs";
2259
+ import { existsSync as existsSync4 } from "fs";
2040
2260
  import { ApiError } from "listpage-http";
2041
2261
  async function runLogin(opts) {
2042
2262
  const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
@@ -2091,169 +2311,13 @@ async function runLogin(opts) {
2091
2311
  );
2092
2312
  const workdir = resolveWorkdirPath();
2093
2313
  const apmDir = workspaceApmDir(workdir);
2094
- if (existsSync3(apmDir)) {
2314
+ if (existsSync4(apmDir)) {
2095
2315
  await syncRemoteDeploymentConfig(workdir, apmDir);
2096
2316
  }
2097
2317
  }
2098
2318
 
2099
2319
  // src/commands/branch.ts
2100
2320
  init_client();
2101
-
2102
- // src/workspace-repos.ts
2103
- import {
2104
- existsSync as existsSync4,
2105
- mkdirSync as mkdirSync3,
2106
- readFileSync as readFileSync5,
2107
- readdirSync as readdirSync3,
2108
- statSync as statSync2,
2109
- writeFileSync as writeFileSync6
2110
- } from "fs";
2111
- import { basename as basename2, dirname as dirname3, join as join6, relative as relative2, resolve as resolve3 } from "path";
2112
- var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
2113
- var WORKSPACE_REPOS_VERSION = 1;
2114
- function manifestPath(workdir) {
2115
- return join6(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
2116
- }
2117
- function absoluteRepoPath(workdir, entry) {
2118
- if (entry.path === "." || entry.path === "") {
2119
- return workdir;
2120
- }
2121
- return resolve3(workdir, entry.path);
2122
- }
2123
- function normalizeRepoEntry(raw) {
2124
- if (!raw || typeof raw !== "object") return null;
2125
- const o = raw;
2126
- if (typeof o.path !== "string" || !o.path.trim()) return null;
2127
- const entry = {
2128
- path: o.path.trim().replace(/\\/g, "/")
2129
- };
2130
- if (typeof o.remoteUrl === "string" && o.remoteUrl.trim()) {
2131
- entry.remoteUrl = o.remoteUrl.trim();
2132
- } else if (o.remoteUrl === null) {
2133
- entry.remoteUrl = null;
2134
- }
2135
- return entry;
2136
- }
2137
- function readManifest(workdir) {
2138
- const path19 = toFsPath(manifestPath(workdir));
2139
- if (!existsSync4(path19)) {
2140
- return null;
2141
- }
2142
- try {
2143
- const raw = JSON.parse(
2144
- readFileSync5(path19, "utf8")
2145
- );
2146
- if (raw?.version !== WORKSPACE_REPOS_VERSION) {
2147
- return null;
2148
- }
2149
- if (raw.kind !== "single" && raw.kind !== "multi") {
2150
- return null;
2151
- }
2152
- if (!Array.isArray(raw.repos) || raw.repos.length === 0) {
2153
- return null;
2154
- }
2155
- if (typeof raw.workdir !== "string" || !raw.workdir.trim()) {
2156
- return null;
2157
- }
2158
- const repos = raw.repos.map((item) => normalizeRepoEntry(item)).filter((item) => item != null);
2159
- if (repos.length === 0) {
2160
- return null;
2161
- }
2162
- return {
2163
- version: WORKSPACE_REPOS_VERSION,
2164
- kind: raw.kind,
2165
- workdir: raw.workdir,
2166
- repos,
2167
- scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt.trim() ? raw.scannedAt : (/* @__PURE__ */ new Date()).toISOString()
2168
- };
2169
- } catch {
2170
- return null;
2171
- }
2172
- }
2173
- function writeManifest(workdir, manifest) {
2174
- const apmDir = toFsPath(workspaceApmDir(workdir));
2175
- mkdirSync3(apmDir, { recursive: true });
2176
- const path19 = toFsPath(manifestPath(workdir));
2177
- writeFileSync6(path19, `${JSON.stringify(manifest, null, 2)}
2178
- `, "utf8");
2179
- }
2180
- function isPathInsideOrEqual(parentAbs, childAbs) {
2181
- const parent = normalizeWorkdirPath(parentAbs);
2182
- const child = normalizeWorkdirPath(childAbs);
2183
- if (child === parent) return true;
2184
- const prefix = parent.endsWith("/") ? parent : `${parent}/`;
2185
- return child.startsWith(prefix);
2186
- }
2187
- function findWorkspaceReposManifestNearPath(startDirInput) {
2188
- let current = resolve3(startDirInput);
2189
- for (; ; ) {
2190
- const candidates = /* @__PURE__ */ new Set([normalizeWorkdirPath(current)]);
2191
- try {
2192
- if (existsSync4(toFsPath(current))) {
2193
- candidates.add(resolveWorkdirPath(current));
2194
- }
2195
- } catch {
2196
- }
2197
- for (const candidate of candidates) {
2198
- const cached = readManifest(candidate);
2199
- if (!cached) continue;
2200
- const cachedWorkdir = resolveWorkdirPath(cached.workdir);
2201
- const candidateNorm = resolveWorkdirPath(candidate);
2202
- if (cachedWorkdir === candidateNorm) {
2203
- return { ...cached, workdir: cachedWorkdir };
2204
- }
2205
- }
2206
- const parent = dirname3(current);
2207
- if (parent === current) {
2208
- return null;
2209
- }
2210
- current = parent;
2211
- }
2212
- }
2213
- function matchWorkspaceRepoEntryForPath(manifest, pathInput) {
2214
- const target = resolveWorkdirPath(pathInput);
2215
- const workdir = resolveWorkdirPath(manifest.workdir);
2216
- let best = null;
2217
- let bestLen = -1;
2218
- for (const entry of manifest.repos) {
2219
- const abs = resolveWorkdirPath(absoluteRepoPath(workdir, entry));
2220
- if (!isPathInsideOrEqual(abs, target)) {
2221
- continue;
2222
- }
2223
- if (abs.length > bestLen) {
2224
- best = entry;
2225
- bestLen = abs.length;
2226
- }
2227
- }
2228
- return best;
2229
- }
2230
- async function enrichWorkspaceReposRemoteUrls(manifest) {
2231
- let changed = false;
2232
- const repos = [];
2233
- for (const entry of manifest.repos) {
2234
- const abs = absoluteRepoPath(manifest.workdir, entry);
2235
- const remoteUrl = await tryReadHttpsGitOriginUrl(abs);
2236
- const next = {
2237
- path: entry.path,
2238
- remoteUrl: remoteUrl || null
2239
- };
2240
- if ((entry.remoteUrl ?? null) !== next.remoteUrl) {
2241
- changed = true;
2242
- }
2243
- repos.push(next);
2244
- }
2245
- const nextManifest = {
2246
- ...manifest,
2247
- repos,
2248
- scannedAt: changed ? (/* @__PURE__ */ new Date()).toISOString() : manifest.scannedAt
2249
- };
2250
- if (changed) {
2251
- writeManifest(manifest.workdir, nextManifest);
2252
- }
2253
- return nextManifest;
2254
- }
2255
-
2256
- // src/commands/branch.ts
2257
2321
  var SESSION_BRANCH_PREFIX = "feat/session-";
2258
2322
  function branchNameForSession(sessionId) {
2259
2323
  const id = sessionId.trim();
@@ -3861,6 +3925,7 @@ function validateDeployPush(o) {
3861
3925
  deploymentRunId: o.deploymentRunId.trim(),
3862
3926
  workdir: o.workdir.trim(),
3863
3927
  environment,
3928
+ ...typeof o.repositoryId === "string" && o.repositoryId.trim() ? { repositoryId: o.repositoryId.trim() } : {},
3864
3929
  ...o.packOnly === true ? { packOnly: true } : {}
3865
3930
  }
3866
3931
  };
@@ -6774,7 +6839,8 @@ async function executeDeploy(options) {
6774
6839
  try {
6775
6840
  const configSyncResult = await syncRemoteDeploymentConfig(
6776
6841
  cwd,
6777
- workspaceApmDir(cwd)
6842
+ workspaceApmDir(cwd),
6843
+ options.repositoryId ? { repositoryId: options.repositoryId } : void 0
6778
6844
  );
6779
6845
  if (configSyncResult.synced && configSyncResult.configName) {
6780
6846
  outputParts.push(
@@ -7049,6 +7115,7 @@ async function handleInboundDeploy(cfg, msg, signal) {
7049
7115
  if (signal.aborted) return;
7050
7116
  const api = createApmApiClient(cfg);
7051
7117
  const deploymentRunId = msg.deploymentRunId;
7118
+ const repositoryId = msg.repositoryId?.trim() || "";
7052
7119
  await api.cli.updateTaskDeploymentStatus({
7053
7120
  id: deploymentRunId,
7054
7121
  status: "DEPLOYING"
@@ -7057,13 +7124,33 @@ async function handleInboundDeploy(cfg, msg, signal) {
7057
7124
  api,
7058
7125
  deploymentRunId,
7059
7126
  run: async (appendLog) => {
7060
- const workdir = requireRemoteWorkdir(msg.workdir);
7127
+ const workspaceRoot = requireRemoteWorkdir(msg.workdir);
7128
+ let cwd = workspaceRoot;
7129
+ if (repositoryId) {
7130
+ const resolved = await resolveDeployCwdForRepository(
7131
+ workspaceRoot,
7132
+ repositoryId,
7133
+ api
7134
+ );
7135
+ if (resolved) {
7136
+ cwd = resolved;
7137
+ appendLog(
7138
+ `[apm] \u591A\u4ED3\u90E8\u7F72\uFF1A\u6309 repositoryId=${repositoryId} \u89E3\u6790 cwd=${cwd}
7139
+ `
7140
+ );
7141
+ } else {
7142
+ appendLog(
7143
+ `[apm] \u672A\u5728 workspace-repos \u547D\u4E2D repositoryId=${repositoryId}\uFF0C\u4F7F\u7528\u5DE5\u4F5C\u7A7A\u95F4\u76EE\u5F55 cwd=${cwd}
7144
+ `
7145
+ );
7146
+ }
7147
+ }
7061
7148
  const displayCommand = resolveDeployCommand(
7062
7149
  msg.environment,
7063
7150
  msg.packOnly
7064
7151
  );
7065
7152
  console.log(
7066
- `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir}`
7153
+ `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${cwd}` + (repositoryId ? ` repositoryId=${repositoryId}` : "")
7067
7154
  );
7068
7155
  console.log(`[apm] deploy command: ${displayCommand}`);
7069
7156
  if (signal.aborted) {
@@ -7071,11 +7158,12 @@ async function handleInboundDeploy(cfg, msg, signal) {
7071
7158
  }
7072
7159
  const deployOptions = {
7073
7160
  env: msg.environment,
7074
- cwd: workdir,
7161
+ cwd,
7075
7162
  packOnly: msg.packOnly,
7076
7163
  captureOutput: true,
7077
7164
  archiveDeployArtifact: true,
7078
- deployTrigger: "connect"
7165
+ deployTrigger: "connect",
7166
+ ...repositoryId ? { repositoryId } : {}
7079
7167
  };
7080
7168
  const output = await executeDeploy(deployOptions);
7081
7169
  if (output.trim()) {
@@ -2718,7 +2718,24 @@ async function resolveRepositoryIdForSync(api, workdirPath) {
2718
2718
  };
2719
2719
  }
2720
2720
  }
2721
- async function syncRemoteDeploymentConfig(workdirPath, apmDir) {
2721
+ async function writeDeploymentConfigContent(apmDir, content, configName) {
2722
+ let parsed;
2723
+ try {
2724
+ parsed = JSON.parse(content);
2725
+ } catch {
2726
+ console.warn(
2727
+ `[apm] \u8FDC\u7A0B\u90E8\u7F72\u914D\u7F6E\u300C${configName}\u300DJSON \u65E0\u6548\uFF08${TEMPLATE_HINT}\uFF09`
2728
+ );
2729
+ return false;
2730
+ }
2731
+ const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
2732
+ writeFileSync6(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
2733
+ `, "utf8");
2734
+ console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${configName}`);
2735
+ console.log("[apm] \u5DF2\u5199\u5165 .apm/apm.config.json");
2736
+ return true;
2737
+ }
2738
+ async function syncRemoteDeploymentConfig(workdirPath, apmDir, options) {
2722
2739
  const cfg = await tryReadApmConfig();
2723
2740
  if (!cfg || !resolveApiKey(cfg)) {
2724
2741
  console.log(
@@ -2728,10 +2745,14 @@ async function syncRemoteDeploymentConfig(workdirPath, apmDir) {
2728
2745
  return { synced: false, repositoryId: null };
2729
2746
  }
2730
2747
  const api = createApmApiClient(cfg);
2731
- const { repositoryId, diagnostic } = await resolveRepositoryIdForSync(
2732
- api,
2733
- workdirPath
2734
- );
2748
+ const preferredId = options?.repositoryId?.trim() || "";
2749
+ let repositoryId = preferredId || null;
2750
+ let diagnostic = null;
2751
+ if (!repositoryId) {
2752
+ const resolved = await resolveRepositoryIdForSync(api, workdirPath);
2753
+ repositoryId = resolved.repositoryId;
2754
+ diagnostic = resolved.diagnostic;
2755
+ }
2735
2756
  if (!repositoryId) {
2736
2757
  console.log(
2737
2758
  `[apm] \u672A\u80FD\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E\uFF08${TEMPLATE_HINT}\uFF09\u3002
@@ -2748,21 +2769,15 @@ ${diagnostic ?? ""}
2748
2769
  );
2749
2770
  return { synced: false, repositoryId };
2750
2771
  }
2751
- let parsed;
2752
- try {
2753
- parsed = JSON.parse(config.content);
2754
- } catch {
2755
- console.warn(
2756
- `[apm] \u8FDC\u7A0B\u90E8\u7F72\u914D\u7F6E\u300C${config.name}\u300DJSON \u65E0\u6548\uFF08${TEMPLATE_HINT}\uFF09`
2757
- );
2772
+ const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
2773
+ const wrote = await writeDeploymentConfigContent(
2774
+ targetApmDir,
2775
+ config.content,
2776
+ config.name
2777
+ );
2778
+ if (!wrote) {
2758
2779
  return { synced: false, repositoryId };
2759
2780
  }
2760
- const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
2761
- const apmConfigPath = toFsPath(join5(targetApmDir, "apm.config.json"));
2762
- writeFileSync6(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
2763
- `, "utf8");
2764
- console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${config.name}`);
2765
- console.log("[apm] \u5DF2\u5199\u5165 .apm/apm.config.json");
2766
2781
  return { synced: true, repositoryId, configName: config.name };
2767
2782
  }
2768
2783
 
@@ -3080,15 +3095,18 @@ function markBranchDone(sessionId, workdir) {
3080
3095
  }
3081
3096
 
3082
3097
  // src/commands/connect/webide-pull-requests.ts
3083
- var TEST_START_ACTIONS = /* @__PURE__ */ new Set([
3098
+ var PRE_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set([
3084
3099
  "generate-cases",
3085
3100
  "run-auto-test",
3086
- // 跳过/直接进手工测试不发 Cursor,PR 延后到部署或验收
3087
- "deploy",
3101
+ // 验收合并前兜底确保 PR 存在
3088
3102
  "accept"
3089
3103
  ]);
3104
+ var POST_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set(["create-pr", "finish-manual-test"]);
3090
3105
  function shouldEnsureWebIdePullRequests(action) {
3091
- return TEST_START_ACTIONS.has(action);
3106
+ return PRE_AGENT_PR_ACTIONS.has(action);
3107
+ }
3108
+ function shouldEnsureWebIdePullRequestsAfterAgent(action) {
3109
+ return POST_AGENT_PR_ACTIONS.has(action);
3092
3110
  }
3093
3111
  async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3094
3112
  const api = createApmApiClient(cfg);
@@ -3101,17 +3119,21 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3101
3119
  "[apm] WebIDE PR\uFF1A\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25:",
3102
3120
  err instanceof Error ? err.message : err
3103
3121
  );
3104
- return;
3122
+ return { ensured: 0, skipped: 0, failed: 1 };
3105
3123
  }
3106
3124
  console.log(
3107
3125
  `[apm] WebIDE PR\uFF1A\u51C6\u5907\u4E3A ${repoRoots.length} \u4E2A\u4ED3\u5E93\u521B\u5EFA feat/task-${taskId}`
3108
3126
  );
3127
+ let ensured = 0;
3128
+ let skipped = 0;
3129
+ let failed = 0;
3109
3130
  for (const gitRoot of repoRoots) {
3110
3131
  const label = formatRepoLabel(workdir, gitRoot);
3111
3132
  try {
3112
3133
  const originUrl = await tryReadGitOriginUrl(gitRoot);
3113
3134
  if (!originUrl) {
3114
3135
  console.warn(`[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u65E0 remote.origin.url\uFF09`);
3136
+ skipped += 1;
3115
3137
  continue;
3116
3138
  }
3117
3139
  const matched = await api.cli.matchRepository({ url: originUrl });
@@ -3120,22 +3142,26 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3120
3142
  console.warn(
3121
3143
  `[apm] WebIDE PR\uFF1A\u8DF3\u8FC7 ${label}\uFF08\u5E73\u53F0\u672A\u767B\u8BB0\u4ED3\u5E93 ${originUrl}\uFF09`
3122
3144
  );
3145
+ skipped += 1;
3123
3146
  continue;
3124
3147
  }
3125
3148
  const pr = await api.cli.createWebIdeDraftPullRequest({
3126
3149
  taskId,
3127
3150
  repositoryId
3128
3151
  });
3152
+ ensured += 1;
3129
3153
  console.log(
3130
3154
  `[apm] WebIDE PR\uFF1A${label} \u2192 #${pr.number} ${pr.state} ${pr.url || ""}`
3131
3155
  );
3132
3156
  } catch (err) {
3157
+ failed += 1;
3133
3158
  console.warn(
3134
3159
  `[apm] WebIDE PR\uFF1A${label} \u5931\u8D25:`,
3135
3160
  err instanceof Error ? err.message : err
3136
3161
  );
3137
3162
  }
3138
3163
  }
3164
+ return { ensured, skipped, failed };
3139
3165
  }
3140
3166
 
3141
3167
  // src/version.ts
@@ -3437,7 +3463,8 @@ var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
3437
3463
  "start-develop",
3438
3464
  "fix-and-retest",
3439
3465
  "report-defect",
3440
- "deploy"
3466
+ "create-pr",
3467
+ "finish-manual-test"
3441
3468
  ]);
3442
3469
  function shouldCommitAfterWebIdeMessage(action) {
3443
3470
  return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
@@ -3651,6 +3678,20 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3651
3678
  `[apm] webide \u81EA\u52A8 push action=${msg.action} repos=${pushed}`
3652
3679
  );
3653
3680
  }
3681
+ if (shouldEnsureWebIdePullRequestsAfterAgent(msg.action)) {
3682
+ if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3683
+ if (!shouldCommitAfterWebIdeMessage(msg.action)) {
3684
+ const pushed = await pushWorkspaceRepos(workdir);
3685
+ console.log(
3686
+ `[apm] webide create-pr \u524D push action=${msg.action} repos=${pushed}`
3687
+ );
3688
+ }
3689
+ const prResult = await ensureWebIdePullRequests(cfg, taskId, workdir);
3690
+ if (prResult.ensured === 0) {
3691
+ const detail = prResult.failed > 0 ? `\u521B\u5EFA PR \u5931\u8D25\uFF08${prResult.failed} \u4E2A\u4ED3\u5E93\u51FA\u9519\uFF09` : "\u672A\u80FD\u521B\u5EFA PR\uFF1A\u672A\u5339\u914D\u5230\u53EF\u767B\u8BB0\u7684\u4ED3\u5E93\uFF0C\u8BF7\u68C0\u67E5\u5DE5\u4F5C\u533A git remote \u4E0E\u5E73\u53F0\u4ED3\u5E93\u767B\u8BB0";
3692
+ throw new Error(detail);
3693
+ }
3694
+ }
3654
3695
  if (projectId) {
3655
3696
  try {
3656
3697
  await syncProjectDocumentsPush(cfg, workdir, void 0, { projectId });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "7.1.18",
3
+ "version": "7.1.20",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,