ai-project-manage-cli 8.1.22 → 8.1.23

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
@@ -1576,27 +1576,35 @@ async function pushCurrentBranch(cwd) {
1576
1576
  // src/utils/home-repository.ts
1577
1577
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
1578
1578
  import { join as join9, resolve as resolve3 } from "path";
1579
- var HOME_REPOSITORY_JSON = "/home/repository.json";
1579
+ var DEFAULT_REPOS_MANIFEST = "/home/repository.json";
1580
1580
  function isSimpleChildDirName(name) {
1581
1581
  if (!name || name === "." || name === "..") return false;
1582
1582
  if (name.includes("/") || name.includes("\\")) return false;
1583
1583
  return true;
1584
1584
  }
1585
- function loadHomeRepositoryEntries() {
1586
- const path = toFsPath(HOME_REPOSITORY_JSON);
1585
+ function resolveReposManifestPath(explicit) {
1586
+ const fromOpt = explicit?.trim() || "";
1587
+ if (fromOpt) return fromOpt;
1588
+ const fromEnv = process.env.APM_REPOS_MANIFEST?.trim() || "";
1589
+ if (fromEnv) return fromEnv;
1590
+ return DEFAULT_REPOS_MANIFEST;
1591
+ }
1592
+ function loadHomeRepositoryEntries(manifestPath) {
1593
+ const label = manifestPath.trim() || DEFAULT_REPOS_MANIFEST;
1594
+ const path = toFsPath(label);
1587
1595
  if (!existsSync6(path)) {
1588
- throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${HOME_REPOSITORY_JSON}`);
1596
+ throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${label}`);
1589
1597
  }
1590
1598
  let parsed;
1591
1599
  try {
1592
1600
  parsed = JSON.parse(readFileSync5(path, "utf8"));
1593
1601
  } catch (err) {
1594
1602
  const detail = err instanceof Error ? err.message : String(err);
1595
- throw new Error(`[apm] \u89E3\u6790 ${HOME_REPOSITORY_JSON} \u5931\u8D25: ${detail}`);
1603
+ throw new Error(`[apm] \u89E3\u6790 ${label} \u5931\u8D25: ${detail}`);
1596
1604
  }
1597
1605
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1598
1606
  throw new Error(
1599
- `[apm] ${HOME_REPOSITORY_JSON} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
1607
+ `[apm] ${label} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
1600
1608
  );
1601
1609
  }
1602
1610
  const entries = [];
@@ -1606,41 +1614,28 @@ function loadHomeRepositoryEntries() {
1606
1614
  const pathKey = rawKey.trim();
1607
1615
  if (!isSimpleChildDirName(pathKey)) {
1608
1616
  throw new Error(
1609
- `[apm] ${HOME_REPOSITORY_JSON} \u975E\u6CD5\u76EE\u5F55 key: ${JSON.stringify(rawKey)}\uFF08\u987B\u4E3A\u5F53\u524D\u4E0B\u7EA7\u76EE\u5F55\u540D\uFF09`
1617
+ `[apm] ${label} \u975E\u6CD5\u76EE\u5F55 key: ${JSON.stringify(rawKey)}\uFF08\u987B\u4E3A\u5F53\u524D\u4E0B\u7EA7\u76EE\u5F55\u540D\uFF09`
1610
1618
  );
1611
1619
  }
1612
1620
  if (typeof rawVal !== "string" || !rawVal.trim()) {
1613
1621
  throw new Error(
1614
- `[apm] ${HOME_REPOSITORY_JSON}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
1622
+ `[apm] ${label}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
1615
1623
  );
1616
1624
  }
1617
1625
  entries.push({ path: pathKey, remoteUrl: rawVal.trim() });
1618
1626
  }
1619
1627
  entries.sort((a, b) => a.path.localeCompare(b.path));
1620
1628
  if (entries.length === 0) {
1621
- throw new Error(`[apm] ${HOME_REPOSITORY_JSON} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
1629
+ throw new Error(`[apm] ${label} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
1622
1630
  }
1623
1631
  return entries;
1624
1632
  }
1625
- function resolveHomeRepositoryAbsolutePaths(workdirInput, entries = loadHomeRepositoryEntries()) {
1633
+ function resolveHomeRepositoryAbsolutePaths(workdirInput, entries) {
1626
1634
  const workdir = resolveWorkdirPath(workdirInput);
1627
1635
  return entries.map((entry) => resolve3(join9(workdir, entry.path)));
1628
1636
  }
1629
- function tryLoadHomeRepositoryEntries() {
1630
- const path = toFsPath(HOME_REPOSITORY_JSON);
1631
- if (!existsSync6(path)) return null;
1632
- return loadHomeRepositoryEntries();
1633
- }
1634
1637
 
1635
1638
  // src/utils/workspace-repos.ts
1636
- import {
1637
- existsSync as existsSync7,
1638
- mkdirSync as mkdirSync5,
1639
- readFileSync as readFileSync6,
1640
- readdirSync as readdirSync3,
1641
- statSync as statSync3,
1642
- writeFileSync as writeFileSync4
1643
- } from "fs";
1644
1639
  import { basename, dirname as dirname4, join as join10, relative, resolve as resolve4 } from "path";
1645
1640
 
1646
1641
  // src/utils/git-remote.ts
@@ -1649,96 +1644,9 @@ import { promisify as promisify2 } from "util";
1649
1644
  var execFileAsync2 = promisify2(execFile2);
1650
1645
 
1651
1646
  // src/utils/workspace-repos.ts
1652
- var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
1653
- var WORKSPACE_REPOS_VERSION = 1;
1654
- function manifestPath(workdir) {
1655
- return join10(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
1656
- }
1657
- function absoluteRepoPath(workdir, entry) {
1658
- return resolve4(workdir, entry.path);
1659
- }
1660
1647
  function toRelativeRepoPath(workdir, repoAbs) {
1661
1648
  return relative(workdir, repoAbs).replace(/\\/g, "/");
1662
1649
  }
1663
- function normalizeRepoEntry(raw) {
1664
- if (!raw || typeof raw !== "object") return null;
1665
- const o = raw;
1666
- if (typeof o.path !== "string" || !o.path.trim()) return null;
1667
- const path = o.path.trim().replace(/\\/g, "/");
1668
- if (path === "." || path.includes("/")) return null;
1669
- const entry = { path };
1670
- if (typeof o.remoteUrl === "string" && o.remoteUrl.trim()) {
1671
- entry.remoteUrl = o.remoteUrl.trim();
1672
- } else if (o.remoteUrl === null) {
1673
- entry.remoteUrl = null;
1674
- }
1675
- return entry;
1676
- }
1677
- function readManifest(workdir) {
1678
- const path = toFsPath(manifestPath(workdir));
1679
- if (!existsSync7(path)) {
1680
- return null;
1681
- }
1682
- try {
1683
- const raw = JSON.parse(
1684
- readFileSync6(path, "utf8")
1685
- );
1686
- if (raw?.version !== WORKSPACE_REPOS_VERSION) {
1687
- return null;
1688
- }
1689
- if (raw.kind !== "multi") {
1690
- return null;
1691
- }
1692
- if (!Array.isArray(raw.repos) || raw.repos.length === 0) {
1693
- return null;
1694
- }
1695
- if (typeof raw.workdir !== "string" || !raw.workdir.trim()) {
1696
- return null;
1697
- }
1698
- const repos = raw.repos.map((item) => normalizeRepoEntry(item)).filter((item) => item != null);
1699
- if (repos.length === 0) {
1700
- return null;
1701
- }
1702
- return {
1703
- version: WORKSPACE_REPOS_VERSION,
1704
- kind: "multi",
1705
- workdir: raw.workdir,
1706
- repos,
1707
- scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt.trim() ? raw.scannedAt : (/* @__PURE__ */ new Date()).toISOString()
1708
- };
1709
- } catch {
1710
- return null;
1711
- }
1712
- }
1713
- function loadWorkspaceReposCache(workdirInput) {
1714
- const workdir = resolveWorkdirPath(workdirInput);
1715
- const cached = readManifest(workdir);
1716
- if (!cached) {
1717
- throw new Error(
1718
- `[apm] \u672A\u627E\u5230\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 .apm/${WORKSPACE_REPOS_MANIFEST}\uFF0C\u8BF7\u5148\u6267\u884C\u4ED3\u5E93\u68C0\u6D4B`
1719
- );
1720
- }
1721
- if (normalizeWorkdirPath(cached.workdir) !== normalizeWorkdirPath(workdir)) {
1722
- throw new Error(
1723
- `[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 workdir \u4E0D\u5339\u914D\uFF1A\u7F13\u5B58=${cached.workdir} \u5F53\u524D=${workdir}\uFF0C\u8BF7\u5F3A\u5236\u91CD\u65B0\u626B\u63CF`
1724
- );
1725
- }
1726
- if (!Array.isArray(cached.repos) || cached.repos.length === 0) {
1727
- throw new Error(`[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58\u65E0\u6548\uFF08repos \u4E3A\u7A7A\uFF09\uFF0C\u8BF7\u5F3A\u5236\u91CD\u65B0\u626B\u63CF`);
1728
- }
1729
- console.log(
1730
- `[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u7F13\u5B58 repos=${cached.repos.map((r) => r.path).join(", ")}`
1731
- );
1732
- return {
1733
- ...cached,
1734
- workdir
1735
- };
1736
- }
1737
- function resolveWorkspaceRepoAbsolutePaths(manifest) {
1738
- return manifest.repos.map(
1739
- (entry) => absoluteRepoPath(manifest.workdir, entry)
1740
- );
1741
- }
1742
1650
  function formatRepoLabel(workdir, repoAbs) {
1743
1651
  const rel = toRelativeRepoPath(resolveWorkdirPath(workdir), repoAbs);
1744
1652
  return rel || basename(repoAbs) || repoAbs;
@@ -1746,14 +1654,19 @@ function formatRepoLabel(workdir, repoAbs) {
1746
1654
 
1747
1655
  // src/commands/branch.ts
1748
1656
  var TASK_BRANCH_PREFIX = "feat/task-";
1749
- async function syncTaskRepos(workdir, message) {
1657
+ async function syncTaskRepos(workdir, options = {}) {
1658
+ const opts = typeof options === "string" ? { message: options } : options;
1750
1659
  const workdirPath = resolveWorkdirPath(workdir);
1751
- const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
1660
+ const repoRoots = resolveTaskBranchRepoRoots(
1661
+ workdirPath,
1662
+ opts.reposManifest
1663
+ );
1752
1664
  let committed = 0;
1753
1665
  let commitSkipped = 0;
1754
1666
  let pushed = 0;
1755
1667
  let pushSkipped = 0;
1756
1668
  const failed = [];
1669
+ const message = opts.message;
1757
1670
  for (const gitRoot of repoRoots) {
1758
1671
  const label = formatRepoLabel(workdirPath, gitRoot);
1759
1672
  try {
@@ -1811,17 +1724,14 @@ async function ensureTaskFeatureBranch(branch, projectId, options) {
1811
1724
  await ensureFeatureBranch(gitRoot, branch, projectId);
1812
1725
  return branch;
1813
1726
  }
1814
- function resolveTaskBranchRepoRoots(workdirPath) {
1815
- const entries = tryLoadHomeRepositoryEntries();
1816
- if (entries) {
1817
- const roots = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
1818
- console.log(
1819
- `[apm] \u8BFB\u53D6 ${HOME_REPOSITORY_JSON} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
1820
- );
1821
- return roots;
1822
- }
1823
- const manifest = loadWorkspaceReposCache(workdirPath);
1824
- return resolveWorkspaceRepoAbsolutePaths(manifest);
1727
+ function resolveTaskBranchRepoRoots(workdirPath, reposManifest) {
1728
+ const manifestPath = resolveReposManifestPath(reposManifest);
1729
+ const entries = loadHomeRepositoryEntries(manifestPath);
1730
+ const roots = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
1731
+ console.log(
1732
+ `[apm] \u8BFB\u53D6 ${manifestPath} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
1733
+ );
1734
+ return roots;
1825
1735
  }
1826
1736
  async function runTaskBranch(taskId, projectId, options = {}) {
1827
1737
  const trimmedTaskId = taskId.trim();
@@ -1837,7 +1747,10 @@ async function runTaskBranch(taskId, projectId, options = {}) {
1837
1747
  }
1838
1748
  const cwd = options.cwd ?? process.cwd();
1839
1749
  const workdirPath = resolveWorkdirPath(cwd);
1840
- const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
1750
+ const repoRoots = resolveTaskBranchRepoRoots(
1751
+ workdirPath,
1752
+ options.reposManifest
1753
+ );
1841
1754
  if (repoRoots.length === 0) {
1842
1755
  throw new Error("[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7279\u6027\u5206\u652F");
1843
1756
  }
@@ -1876,19 +1789,18 @@ async function runTaskBranch(taskId, projectId, options = {}) {
1876
1789
  // src/commands/ensure-task-branch.ts
1877
1790
  async function runEnsureTaskBranchCli(options = {}) {
1878
1791
  const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
1879
- const taskId = options.taskId?.trim() || process.env.APM_TASK_ID?.trim() || "";
1880
- const projectId = options.projectId?.trim() || process.env.APM_PROJECT_ID?.trim() || "";
1792
+ const taskId = options.taskId?.trim() || "";
1793
+ const projectId = options.projectId?.trim() || "";
1881
1794
  if (!taskId) {
1882
- throw new Error(
1883
- "[apm] \u7F3A\u5C11 taskId\uFF1A\u8BF7\u4F20\u5165\u53C2\u6570\u6216\u8BBE\u7F6E APM_TASK_ID"
1884
- );
1795
+ throw new Error("[apm] \u7F3A\u5C11 taskId\uFF1A\u8BF7\u4F20\u5165\u53C2\u6570");
1885
1796
  }
1886
1797
  if (!projectId) {
1887
- throw new Error(
1888
- "[apm] \u7F3A\u5C11 projectId\uFF1A\u8BF7\u4F20\u5165\u53C2\u6570\u6216\u8BBE\u7F6E APM_PROJECT_ID"
1889
- );
1798
+ throw new Error("[apm] \u7F3A\u5C11 projectId\uFF1A\u8BF7\u4F20\u5165\u53C2\u6570");
1890
1799
  }
1891
- const result = await runTaskBranch(taskId, projectId, { cwd: workdir });
1800
+ const result = await runTaskBranch(taskId, projectId, {
1801
+ cwd: workdir,
1802
+ reposManifest: options.reposManifest
1803
+ });
1892
1804
  console.log(
1893
1805
  `[apm] \u7279\u6027\u5206\u652F\u5DF2\u5C31\u7EEA\uFF08\u5168\u90E8 ${result.repos.length} \u4E2A\u4ED3\uFF09 branch=${result.branch} projectId=${result.projectId}`
1894
1806
  );
@@ -1896,7 +1808,7 @@ async function runEnsureTaskBranchCli(options = {}) {
1896
1808
 
1897
1809
  // src/utils/user-prompt-cache.ts
1898
1810
  import { createHash } from "crypto";
1899
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1811
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1900
1812
  import { join as join11 } from "path";
1901
1813
  import { tmpdir } from "os";
1902
1814
  var CACHE_ROOT = join11(tmpdir(), "apm-hooks");
@@ -1918,9 +1830,9 @@ function formatUserPromptAsCommitMessage(prompt) {
1918
1830
  }
1919
1831
  function loadUserPromptCommitMessage(workdirInput) {
1920
1832
  const path = toFsPath(userPromptCachePath(workdirInput));
1921
- if (!existsSync8(path)) return null;
1833
+ if (!existsSync7(path)) return null;
1922
1834
  try {
1923
- const raw = readFileSync7(path, "utf8");
1835
+ const raw = readFileSync6(path, "utf8");
1924
1836
  const msg = formatUserPromptAsCommitMessage(raw);
1925
1837
  return msg || null;
1926
1838
  } catch {
@@ -1959,7 +1871,10 @@ async function runCommitTaskReposCli(options = {}) {
1959
1871
  if (message) {
1960
1872
  console.log(`[apm] commit message: ${message}`);
1961
1873
  }
1962
- const result = await syncTaskRepos(workdir, message);
1874
+ const result = await syncTaskRepos(workdir, {
1875
+ message,
1876
+ reposManifest: options.reposManifest
1877
+ });
1963
1878
  console.log(
1964
1879
  `[apm] \u591A\u4ED3\u6536\u5C3E\u5B8C\u6210 committed=${result.committed} commitSkipped=${result.commitSkipped} pushed=${result.pushed} pushSkipped=${result.pushSkipped} failed=${result.failed.length} repos=${result.repos.length}`
1965
1880
  );
@@ -2007,30 +1922,35 @@ function buildProgram() {
2007
1922
  await runUpdateSkills();
2008
1923
  });
2009
1924
  program.command("ensure-task-branch").description(
2010
- "\u786E\u4FDD\u5DE5\u4F5C\u533A\u5404\u4ED3\u4F4D\u4E8E feat/task-<\u4EFB\u52A1ID>\uFF08\u4E0D\u5B58\u5728\u5219\u57FA\u4E8E\u9879\u76EE ID \u540C\u540D\u5206\u652F\u4ECE\u8FDC\u7A0B\u521B\u5EFA\uFF09\uFF1B\u4F9B Cursor hooks \u8C03\u7528"
2011
- ).argument(
2012
- "[taskId]",
2013
- "\u4EFB\u52A1 ID\uFF08\u521B\u5EFA/\u5207\u6362\u5206\u652F\uFF09\uFF1B\u7701\u7565\u65F6\u8BFB APM_TASK_ID"
2014
- ).argument(
2015
- "[projectId]",
2016
- "\u9879\u76EE ID\uFF08\u4EC5\u521B\u5EFA\u65F6\u7528\uFF1B\u89C4\u8303\u4E0E\u9879\u76EE\u5206\u652F\u540C\u540D\uFF09\uFF1B\u7701\u7565\u65F6\u8BFB APM_PROJECT_ID"
2017
- ).option("--cwd <dir>", "\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").action(
1925
+ "\u6309\u591A\u4ED3\u6E05\u5355\u786E\u4FDD\u5404\u4ED3\u4F4D\u4E8E feat/task-<\u4EFB\u52A1ID>\uFF08\u4E0D\u5B58\u5728\u5219\u57FA\u4E8E\u9879\u76EE\u5206\u652F\u4ECE\u8FDC\u7A0B\u521B\u5EFA\uFF09\u3002ACP \u4E0D\u4F1A\u8DD1 Cursor hooks\uFF0C\u5F00\u59CB\u4EFB\u52A1\u65F6\u7531\u670D\u52A1\u7AEF\u6216\u624B\u52A8\u8C03\u7528\u672C\u547D\u4EE4"
1926
+ ).argument("<taskId>", "\u4EFB\u52A1 ID\uFF08\u521B\u5EFA/\u5207\u6362\u5206\u652F\uFF09").argument(
1927
+ "<projectId>",
1928
+ "\u9879\u76EE ID\uFF08\u4EC5\u521B\u5EFA\u65F6\u7528\uFF1B\u89C4\u8303\u4E0E\u9879\u76EE\u5206\u652F\u540C\u540D\uFF09"
1929
+ ).option("--cwd <dir>", "\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").option(
1930
+ "--repos <file>",
1931
+ "\u591A\u4ED3\u6E05\u5355 JSON\uFF08key=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740\uFF09\uFF1B\u7701\u7565\u65F6\u8BFB APM_REPOS_MANIFEST\uFF0C\u518D\u9ED8\u8BA4 /home/repository.json"
1932
+ ).action(
2018
1933
  async (taskId, projectId, opts) => {
2019
1934
  await runEnsureTaskBranchCli({
2020
1935
  taskId,
2021
1936
  projectId,
2022
- cwd: opts.cwd
1937
+ cwd: opts.cwd,
1938
+ reposManifest: opts.repos
2023
1939
  });
2024
1940
  }
2025
1941
  );
2026
1942
  program.command("commit-task-repos").description(
2027
- "\u6267\u884C\u7ED3\u675F\u6536\u5C3E\uFF1A/home/repository.json \u5404\u4ED3\u6709\u53D8\u66F4\u624D commit\uFF0C\u518D push\uFF08\u65E0\u53D8\u66F4\u4E0D\u7A7A\u63D0\u4EA4\uFF09"
1943
+ "\u6267\u884C\u7ED3\u675F\u6536\u5C3E\uFF1A\u591A\u4ED3\u6E05\u5355\u5404\u4ED3\u6709\u53D8\u66F4\u624D commit\uFF0C\u518D push\uFF08\u65E0\u53D8\u66F4\u4E0D\u7A7A\u63D0\u4EA4\uFF09"
2028
1944
  ).option("--cwd <dir>", "\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").option(
1945
+ "--repos <file>",
1946
+ "\u591A\u4ED3\u6E05\u5355 JSON\uFF1B\u7701\u7565\u65F6\u8BFB APM_REPOS_MANIFEST\uFF0C\u518D\u9ED8\u8BA4 /home/repository.json"
1947
+ ).option(
2029
1948
  "-m, --message <msg>",
2030
1949
  "commit message\uFF1B\u7701\u7565\u5219\u4F18\u5148\u7528\u6237\u95EE\u9898\u7F13\u5B58\uFF0C\u5426\u5219\u6309 APM_TASK_ID / APM_PROJECT_ID \u751F\u6210"
2031
1950
  ).action(async (opts) => {
2032
1951
  await runCommitTaskReposCli({
2033
1952
  cwd: opts.cwd,
1953
+ reposManifest: opts.repos,
2034
1954
  message: opts.message
2035
1955
  });
2036
1956
  });
@@ -10,16 +10,18 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
10
 
11
11
  git rev-parse --git-dir >/dev/null
12
12
 
13
+ # 有未提交改动时先 stash(含 untracked),再切/建分支,避免污染目标分支工作区
14
+ if [[ -n "$(git status --porcelain)" ]]; then
15
+ git stash push -u -m "apm: ensure ${branch}"
16
+ echo "[apm] 已 stash 未提交改动,再确认分支 ${branch}"
17
+ fi
18
+
13
19
  current="$(git rev-parse --abbrev-ref HEAD)"
14
20
  if [[ "$current" == "$branch" ]]; then
15
21
  echo "[apm] 已在分支 ${branch}"
16
22
  else
17
23
  bash "$SCRIPT_DIR/ensure_baseline.sh" "$baseline"
18
24
 
19
- if [[ -n "$(git status --porcelain)" ]]; then
20
- git stash push -u -m "apm: switch to ${branch}"
21
- fi
22
-
23
25
  if git ls-remote --heads origin "$branch" | grep -q .; then
24
26
  git fetch origin "$branch"
25
27
  git checkout -B "$branch" "origin/${branch}"
@@ -518,27 +518,35 @@ async function pushCurrentBranch(cwd) {
518
518
  // src/utils/home-repository.ts
519
519
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
520
520
  import { join as join4, resolve as resolve3 } from "path";
521
- var HOME_REPOSITORY_JSON = "/home/repository.json";
521
+ var DEFAULT_REPOS_MANIFEST = "/home/repository.json";
522
522
  function isSimpleChildDirName(name) {
523
523
  if (!name || name === "." || name === "..") return false;
524
524
  if (name.includes("/") || name.includes("\\")) return false;
525
525
  return true;
526
526
  }
527
- function loadHomeRepositoryEntries() {
528
- const path = toFsPath(HOME_REPOSITORY_JSON);
527
+ function resolveReposManifestPath(explicit) {
528
+ const fromOpt = explicit?.trim() || "";
529
+ if (fromOpt) return fromOpt;
530
+ const fromEnv = process.env.APM_REPOS_MANIFEST?.trim() || "";
531
+ if (fromEnv) return fromEnv;
532
+ return DEFAULT_REPOS_MANIFEST;
533
+ }
534
+ function loadHomeRepositoryEntries(manifestPath2) {
535
+ const label = manifestPath2.trim() || DEFAULT_REPOS_MANIFEST;
536
+ const path = toFsPath(label);
529
537
  if (!existsSync2(path)) {
530
- throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${HOME_REPOSITORY_JSON}`);
538
+ throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${label}`);
531
539
  }
532
540
  let parsed;
533
541
  try {
534
542
  parsed = JSON.parse(readFileSync2(path, "utf8"));
535
543
  } catch (err) {
536
544
  const detail = err instanceof Error ? err.message : String(err);
537
- throw new Error(`[apm] \u89E3\u6790 ${HOME_REPOSITORY_JSON} \u5931\u8D25: ${detail}`);
545
+ throw new Error(`[apm] \u89E3\u6790 ${label} \u5931\u8D25: ${detail}`);
538
546
  }
539
547
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
540
548
  throw new Error(
541
- `[apm] ${HOME_REPOSITORY_JSON} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
549
+ `[apm] ${label} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
542
550
  );
543
551
  }
544
552
  const entries = [];
@@ -548,31 +556,26 @@ function loadHomeRepositoryEntries() {
548
556
  const pathKey = rawKey.trim();
549
557
  if (!isSimpleChildDirName(pathKey)) {
550
558
  throw new Error(
551
- `[apm] ${HOME_REPOSITORY_JSON} \u975E\u6CD5\u76EE\u5F55 key: ${JSON.stringify(rawKey)}\uFF08\u987B\u4E3A\u5F53\u524D\u4E0B\u7EA7\u76EE\u5F55\u540D\uFF09`
559
+ `[apm] ${label} \u975E\u6CD5\u76EE\u5F55 key: ${JSON.stringify(rawKey)}\uFF08\u987B\u4E3A\u5F53\u524D\u4E0B\u7EA7\u76EE\u5F55\u540D\uFF09`
552
560
  );
553
561
  }
554
562
  if (typeof rawVal !== "string" || !rawVal.trim()) {
555
563
  throw new Error(
556
- `[apm] ${HOME_REPOSITORY_JSON}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
564
+ `[apm] ${label}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
557
565
  );
558
566
  }
559
567
  entries.push({ path: pathKey, remoteUrl: rawVal.trim() });
560
568
  }
561
569
  entries.sort((a, b) => a.path.localeCompare(b.path));
562
570
  if (entries.length === 0) {
563
- throw new Error(`[apm] ${HOME_REPOSITORY_JSON} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
571
+ throw new Error(`[apm] ${label} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
564
572
  }
565
573
  return entries;
566
574
  }
567
- function resolveHomeRepositoryAbsolutePaths(workdirInput, entries = loadHomeRepositoryEntries()) {
575
+ function resolveHomeRepositoryAbsolutePaths(workdirInput, entries) {
568
576
  const workdir = resolveWorkdirPath(workdirInput);
569
577
  return entries.map((entry) => resolve3(join4(workdir, entry.path)));
570
578
  }
571
- function tryLoadHomeRepositoryEntries() {
572
- const path = toFsPath(HOME_REPOSITORY_JSON);
573
- if (!existsSync2(path)) return null;
574
- return loadHomeRepositoryEntries();
575
- }
576
579
 
577
580
  // src/utils/workspace-repos.ts
578
581
  import {
@@ -857,21 +860,26 @@ function formatRepoLabel(workdir, repoAbs) {
857
860
 
858
861
  // src/commands/branch.ts
859
862
  var TASK_BRANCH_PREFIX = "feat/task-";
860
- async function commitWorkspaceReposIfDirty(workdir, message) {
861
- const result = await commitTaskReposIfDirty(workdir, message);
863
+ async function commitWorkspaceReposIfDirty(workdir, message, reposManifest) {
864
+ const result = await commitTaskReposIfDirty(workdir, message, reposManifest);
862
865
  return result.committed;
863
866
  }
864
- async function commitTaskReposIfDirty(workdir, message) {
865
- return syncTaskRepos(workdir, message);
867
+ async function commitTaskReposIfDirty(workdir, message, reposManifest) {
868
+ return syncTaskRepos(workdir, { message, reposManifest });
866
869
  }
867
- async function syncTaskRepos(workdir, message) {
870
+ async function syncTaskRepos(workdir, options = {}) {
871
+ const opts = typeof options === "string" ? { message: options } : options;
868
872
  const workdirPath = resolveWorkdirPath(workdir);
869
- const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
873
+ const repoRoots = resolveTaskBranchRepoRoots(
874
+ workdirPath,
875
+ opts.reposManifest
876
+ );
870
877
  let committed = 0;
871
878
  let commitSkipped = 0;
872
879
  let pushed = 0;
873
880
  let pushSkipped = 0;
874
881
  const failed = [];
882
+ const message = opts.message;
875
883
  for (const gitRoot of repoRoots) {
876
884
  const label = formatRepoLabel(workdirPath, gitRoot);
877
885
  try {
@@ -960,17 +968,14 @@ async function resolveProjectIdForTask(taskId) {
960
968
  }
961
969
  return projectId;
962
970
  }
963
- function resolveTaskBranchRepoRoots(workdirPath) {
964
- const entries = tryLoadHomeRepositoryEntries();
965
- if (entries) {
966
- const roots = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
967
- console.log(
968
- `[apm] \u8BFB\u53D6 ${HOME_REPOSITORY_JSON} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
969
- );
970
- return roots;
971
- }
972
- const manifest = loadWorkspaceReposCache(workdirPath);
973
- return resolveWorkspaceRepoAbsolutePaths(manifest);
971
+ function resolveTaskBranchRepoRoots(workdirPath, reposManifest) {
972
+ const manifestPath2 = resolveReposManifestPath(reposManifest);
973
+ const entries = loadHomeRepositoryEntries(manifestPath2);
974
+ const roots = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
975
+ console.log(
976
+ `[apm] \u8BFB\u53D6 ${manifestPath2} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
977
+ );
978
+ return roots;
974
979
  }
975
980
  async function runTaskBranch(taskId, projectId, options = {}) {
976
981
  const trimmedTaskId = taskId.trim();
@@ -986,7 +991,10 @@ async function runTaskBranch(taskId, projectId, options = {}) {
986
991
  }
987
992
  const cwd = options.cwd ?? process.cwd();
988
993
  const workdirPath = resolveWorkdirPath(cwd);
989
- const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
994
+ const repoRoots = resolveTaskBranchRepoRoots(
995
+ workdirPath,
996
+ options.reposManifest
997
+ );
990
998
  if (repoRoots.length === 0) {
991
999
  throw new Error("[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7279\u6027\u5206\u652F");
992
1000
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "8.1.22",
3
+ "version": "8.1.23",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,13 +0,0 @@
1
- # CLI template(新版星图 / ACP)
2
-
3
- 本目录供**新版** Cursor ACP / 星图流程使用,与历史 WebIDE `apm connect` 路径**解耦**:
4
-
5
- | 内容 | 用途 |
6
- |------|------|
7
- | `mcp.json` | 远程 MCP(`x-task-id` / `x-project-id`) |
8
- | `hooks/` | ACP hooks:开对话切分支、结束 commit/push |
9
- | `skills/` | 星图等新版 skills(如 `starmap-requirement-align`) |
10
-
11
- 历史 WebIDE 指南与规则仍由平台 `agent-config` 灌库,经 `apm update-skills` 写入 `.apm/`,**不读**本 template。
12
-
13
- 请勿把本目录 hooks/mcp 默认拷进历史 WebIDE 工作区,以免与旧流程混用。
@@ -1,112 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Cursor stop(每次 Agent 执行结束一次):
4
- * 对 `/home/repository.json` 各仓 commit-if-dirty + push。
5
- * 无变更不空 commit;单仓失败继续其它仓;本 hook 始终 exit 0。
6
- *
7
- * ACP 若不触发 stop:由启动方在进程结束后调用
8
- * apm commit-task-repos --cwd <workdir>
9
- */
10
- "use strict";
11
-
12
- const { spawnSync } = require("node:child_process");
13
- const { existsSync, readFileSync } = require("node:fs");
14
- const { join, resolve } = require("node:path");
15
-
16
- function respond(obj) {
17
- process.stdout.write(JSON.stringify(obj ?? {}));
18
- }
19
-
20
- function readStdin() {
21
- try {
22
- return readFileSync(0, "utf8");
23
- } catch {
24
- return "";
25
- }
26
- }
27
-
28
- function resolveWorkdir(payload) {
29
- const cwd = typeof payload.cwd === "string" ? payload.cwd.trim() : "";
30
- if (cwd) return resolve(cwd);
31
- const roots = Array.isArray(payload.workspace_roots)
32
- ? payload.workspace_roots
33
- : [];
34
- for (const root of roots) {
35
- if (typeof root === "string" && root.trim()) return resolve(root.trim());
36
- }
37
- return process.cwd();
38
- }
39
-
40
- /** APM 工作区根 = ACP 启动目录,其下必须有 `.apm`(不向上查找) */
41
- function requireApmWorkdir(start) {
42
- const workdir = resolve(start);
43
- if (!existsSync(join(workdir, ".apm"))) return null;
44
- return workdir;
45
- }
46
-
47
- function whichApm() {
48
- const r = spawnSync("bash", ["-lc", "command -v apm"], {
49
- encoding: "utf8",
50
- });
51
- if (r.status !== 0) return "";
52
- return (r.stdout || "").trim();
53
- }
54
-
55
- function hasTaskContext() {
56
- return Boolean(
57
- (process.env.APM_TASK_ID || "").trim() ||
58
- (process.env.APM_PROJECT_ID || "").trim()
59
- );
60
- }
61
-
62
- function main() {
63
- let input = {};
64
- try {
65
- const raw = readStdin();
66
- if (raw.trim()) input = JSON.parse(raw);
67
- } catch {
68
- /* 收尾:输入坏了也尽量继续 */
69
- }
70
-
71
- const workdirHint = resolveWorkdir(input);
72
- const workdir = requireApmWorkdir(workdirHint);
73
- if (!workdir) {
74
- process.stderr.write(
75
- `[apm] ACP 启动目录下未找到 .apm,跳过收尾: ${resolve(workdirHint)}\n`
76
- );
77
- respond({});
78
- process.exit(0);
79
- }
80
-
81
- if (!hasTaskContext() && !existsSync("/home/repository.json")) {
82
- respond({});
83
- process.exit(0);
84
- }
85
-
86
- const apm = whichApm();
87
- if (!apm) {
88
- process.stderr.write("[apm] commit-task-repos:未找到 apm,跳过收尾\n");
89
- respond({});
90
- process.exit(0);
91
- }
92
-
93
- const result = spawnSync(apm, ["commit-task-repos", "--cwd", workdir], {
94
- encoding: "utf8",
95
- env: process.env,
96
- });
97
-
98
- if (result.stdout?.trim()) process.stderr.write(result.stdout);
99
- if (result.stderr?.trim()) process.stderr.write(result.stderr);
100
-
101
- respond({});
102
- process.exit(0);
103
- }
104
-
105
- try {
106
- main();
107
- } catch (err) {
108
- const detail = err instanceof Error ? err.message : String(err);
109
- process.stderr.write(`[apm] after-agent-commit-repos 异常: ${detail}\n`);
110
- respond({});
111
- process.exit(0);
112
- }
@@ -1,206 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Cursor beforeSubmitPrompt(对话 / 本轮用户提问开始之前):
4
- * 1. 缓存用户问题(供结束时 commit message)
5
- * 2. 确保 /home/repository.json 内各仓位于 feat/task-<APM_TASK_ID>
6
- *
7
- * 失败则 continue:false,本轮不进入 Agent。
8
- * ACP 若不触发本 hook:启动对话前由宿主执行
9
- * apm ensure-task-branch && 写入 user-prompt 缓存文件
10
- */
11
- "use strict";
12
-
13
- const { createHash } = require("node:crypto");
14
- const { spawnSync } = require("node:child_process");
15
- const {
16
- existsSync,
17
- mkdirSync,
18
- readFileSync,
19
- writeFileSync,
20
- } = require("node:fs");
21
- const { tmpdir } = require("node:os");
22
- const { join, resolve } = require("node:path");
23
-
24
- function respond(obj) {
25
- process.stdout.write(JSON.stringify(obj));
26
- }
27
-
28
- function allow() {
29
- respond({ continue: true });
30
- process.exit(0);
31
- }
32
-
33
- function deny(userMessage) {
34
- respond({
35
- continue: false,
36
- user_message: userMessage,
37
- });
38
- process.exit(0);
39
- }
40
-
41
- function readStdin() {
42
- return readFileSync(0, "utf8");
43
- }
44
-
45
- function resolveWorkdir(payload) {
46
- const roots = Array.isArray(payload.workspace_roots)
47
- ? payload.workspace_roots
48
- : [];
49
- for (const root of roots) {
50
- if (typeof root === "string" && root.trim()) return resolve(root.trim());
51
- }
52
- const cwd = typeof payload.cwd === "string" ? payload.cwd.trim() : "";
53
- if (cwd) return resolve(cwd);
54
- return process.cwd();
55
- }
56
-
57
- /**
58
- * APM 工作区根 = Cursor ACP 启动目录,其下必须有 `.apm`(不向上查找)。
59
- * @returns {string | null}
60
- */
61
- function requireApmWorkdir(start) {
62
- const workdir = resolve(start);
63
- if (!existsSync(join(workdir, ".apm"))) return null;
64
- return workdir;
65
- }
66
-
67
- function promptCachePath(workdir) {
68
- const key = createHash("sha256").update(workdir).digest("hex").slice(0, 16);
69
- return join(tmpdir(), "apm-hooks", key, "user-prompt.txt");
70
- }
71
-
72
- function cacheUserPrompt(workdir, prompt) {
73
- const text = typeof prompt === "string" ? prompt.trim() : "";
74
- if (!text) return;
75
- const path = promptCachePath(workdir);
76
- try {
77
- mkdirSync(join(path, ".."), { recursive: true });
78
- writeFileSync(path, `${text}\n`, "utf8");
79
- } catch (err) {
80
- const detail = err instanceof Error ? err.message : String(err);
81
- process.stderr.write(`[apm] 缓存用户问题失败: ${detail}\n`);
82
- }
83
- }
84
-
85
- function expectedBranch(taskId) {
86
- return `feat/task-${taskId}`;
87
- }
88
-
89
- function currentBranch(repoRoot) {
90
- const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
91
- cwd: repoRoot,
92
- encoding: "utf8",
93
- });
94
- if (r.status !== 0) return null;
95
- return (r.stdout || "").trim();
96
- }
97
-
98
- const HOME_REPOSITORY_JSON = "/home/repository.json";
99
-
100
- function listReposNotOnBranch(workdir, branch) {
101
- if (!existsSync(HOME_REPOSITORY_JSON)) return null;
102
- let map;
103
- try {
104
- map = JSON.parse(readFileSync(HOME_REPOSITORY_JSON, "utf8"));
105
- } catch {
106
- return null;
107
- }
108
- if (!map || typeof map !== "object" || Array.isArray(map)) return null;
109
- const keys = Object.keys(map)
110
- .map((k) => String(k).trim())
111
- .filter((k) => k && k !== "." && !k.includes("/") && !k.includes("\\"));
112
- if (keys.length === 0) return null;
113
- const off = [];
114
- for (const rel of keys) {
115
- if (currentBranch(join(workdir, rel)) !== branch) off.push(rel);
116
- }
117
- return off;
118
- }
119
-
120
- function whichApm() {
121
- const r = spawnSync("bash", ["-lc", "command -v apm"], {
122
- encoding: "utf8",
123
- });
124
- if (r.status !== 0) return "";
125
- return (r.stdout || "").trim();
126
- }
127
-
128
- function main() {
129
- let input;
130
- try {
131
- input = JSON.parse(readStdin() || "{}");
132
- } catch {
133
- deny("[apm] beforeSubmitPrompt 输入不是合法 JSON");
134
- }
135
-
136
- const workdirHint = resolveWorkdir(input);
137
- const workdir = requireApmWorkdir(workdirHint);
138
- if (!workdir) {
139
- deny(
140
- `[apm] ACP 启动目录下未找到 .apm:${resolve(workdirHint)}(必须为启动目录/.apm,不向上查找)`
141
- );
142
- }
143
-
144
- cacheUserPrompt(workdir, input.prompt);
145
-
146
- const taskId = (process.env.APM_TASK_ID || "").trim();
147
- const projectId = (process.env.APM_PROJECT_ID || "").trim();
148
-
149
- // 非 APM 任务:只缓存问题(若有),放行
150
- if (!taskId && !projectId) {
151
- allow();
152
- }
153
-
154
- if (!taskId || !projectId) {
155
- deny(
156
- "[apm] 缺少 APM_TASK_ID 或 APM_PROJECT_ID,无法在对话开始前确认特性分支"
157
- );
158
- }
159
-
160
- const branch = expectedBranch(taskId);
161
- const offBranch = listReposNotOnBranch(workdir, branch);
162
- if (Array.isArray(offBranch) && offBranch.length === 0) {
163
- allow();
164
- }
165
-
166
- const apm = whichApm();
167
- if (!apm) {
168
- deny("[apm] 未找到 apm CLI,无法确认特性分支");
169
- }
170
-
171
- if (Array.isArray(offBranch) && offBranch.length > 0) {
172
- process.stderr.write(
173
- `[apm] 对话开始前确认分支 ${branch},未就绪: ${offBranch.join(", ")}\n`
174
- );
175
- }
176
-
177
- const result = spawnSync(
178
- apm,
179
- ["ensure-task-branch", taskId, projectId, "--cwd", workdir],
180
- {
181
- encoding: "utf8",
182
- env: process.env,
183
- }
184
- );
185
-
186
- if (result.stdout?.trim()) process.stderr.write(result.stdout);
187
- if (result.stderr?.trim()) process.stderr.write(result.stderr);
188
-
189
- if (result.status === 0) {
190
- allow();
191
- }
192
-
193
- const detail = `${result.stdout || ""}${result.stderr || ""}`.trim();
194
- deny(
195
- detail
196
- ? `[apm] 对话开始前确认特性分支失败\n${detail}`
197
- : `[apm] 对话开始前确认特性分支失败(exit=${result.status ?? "unknown"})`
198
- );
199
- }
200
-
201
- try {
202
- main();
203
- } catch (err) {
204
- const detail = err instanceof Error ? err.message : String(err);
205
- deny(`[apm] before-prompt-prepare 异常: ${detail}`);
206
- }
@@ -1,18 +0,0 @@
1
- {
2
- "version": 1,
3
- "hooks": {
4
- "beforeSubmitPrompt": [
5
- {
6
- "command": "./hooks/before-prompt-prepare.cjs",
7
- "timeout": 180,
8
- "failClosed": true
9
- }
10
- ],
11
- "stop": [
12
- {
13
- "command": "./hooks/after-agent-commit-repos.cjs",
14
- "timeout": 180
15
- }
16
- ]
17
- }
18
- }
package/template/mcp.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "apm": {
4
- "url": "${env:APM_SERVER}/api/v1/mcp",
5
- "headers": {
6
- "Authorization": "Bearer ${env:APM_API_KEY}",
7
- "x-task-id": "${env:APM_TASK_ID}",
8
- "x-project-id": "${env:APM_PROJECT_ID}"
9
- }
10
- },
11
- "billion-skill": {
12
- "url": "https://wdxnhqgjbvib.sealosgzg.site/api/mcp",
13
- "headers": {
14
- "Authorization": "Bearer sk_ImOQHJZSGncproCHg6Q7ZnE_vGmylWtdYkG6TPkH2Jg"
15
- }
16
- }
17
- }
18
- }