ai-project-manage-cli 8.1.22 → 8.1.24

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
@@ -1430,9 +1430,77 @@ async function runConnect(options) {
1430
1430
  }
1431
1431
  }
1432
1432
 
1433
+ // src/utils/home-repository.ts
1434
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
1435
+ import { join as join8, resolve as resolve3 } from "path";
1436
+ var DEFAULT_REPOS_MANIFEST = "/home/repository.json";
1437
+ function isSimpleChildDirName(name) {
1438
+ if (!name || name === "." || name === "..") return false;
1439
+ if (name.includes("/") || name.includes("\\")) return false;
1440
+ return true;
1441
+ }
1442
+ function resolveReposManifestPath(explicit) {
1443
+ const fromOpt = explicit?.trim() || "";
1444
+ if (fromOpt) return fromOpt;
1445
+ const fromEnv = process.env.APM_REPOS_MANIFEST?.trim() || "";
1446
+ if (fromEnv) return fromEnv;
1447
+ return null;
1448
+ }
1449
+ function resolveCliReposManifestPath(explicit) {
1450
+ return resolveReposManifestPath(explicit) ?? DEFAULT_REPOS_MANIFEST;
1451
+ }
1452
+ function loadHomeRepositoryEntries(manifestPath2) {
1453
+ const label = manifestPath2.trim();
1454
+ if (!label) {
1455
+ throw new Error("[apm] \u591A\u4ED3\u6E05\u5355\u8DEF\u5F84\u4E3A\u7A7A");
1456
+ }
1457
+ const path = toFsPath(label);
1458
+ if (!existsSync6(path)) {
1459
+ throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${label}`);
1460
+ }
1461
+ let parsed;
1462
+ try {
1463
+ parsed = JSON.parse(readFileSync5(path, "utf8"));
1464
+ } catch (err) {
1465
+ const detail = err instanceof Error ? err.message : String(err);
1466
+ throw new Error(`[apm] \u89E3\u6790 ${label} \u5931\u8D25: ${detail}`);
1467
+ }
1468
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1469
+ throw new Error(
1470
+ `[apm] ${label} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
1471
+ );
1472
+ }
1473
+ const entries = [];
1474
+ for (const [rawKey, rawVal] of Object.entries(
1475
+ parsed
1476
+ )) {
1477
+ const pathKey = rawKey.trim();
1478
+ if (!isSimpleChildDirName(pathKey)) {
1479
+ throw new Error(
1480
+ `[apm] ${label} \u975E\u6CD5\u76EE\u5F55 key: ${JSON.stringify(rawKey)}\uFF08\u987B\u4E3A\u5F53\u524D\u4E0B\u7EA7\u76EE\u5F55\u540D\uFF09`
1481
+ );
1482
+ }
1483
+ if (typeof rawVal !== "string" || !rawVal.trim()) {
1484
+ throw new Error(
1485
+ `[apm] ${label}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
1486
+ );
1487
+ }
1488
+ entries.push({ path: pathKey, remoteUrl: rawVal.trim() });
1489
+ }
1490
+ entries.sort((a, b) => a.path.localeCompare(b.path));
1491
+ if (entries.length === 0) {
1492
+ throw new Error(`[apm] ${label} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
1493
+ }
1494
+ return entries;
1495
+ }
1496
+ function resolveHomeRepositoryAbsolutePaths(workdirInput, entries) {
1497
+ const workdir = resolveWorkdirPath(workdirInput);
1498
+ return entries.map((entry) => resolve3(join8(workdir, entry.path)));
1499
+ }
1500
+
1433
1501
  // src/utils/git.ts
1434
1502
  import { execFile } from "child_process";
1435
- import { dirname as dirname3, join as join8 } from "path";
1503
+ import { dirname as dirname3, join as join9 } from "path";
1436
1504
  import { fileURLToPath as fileURLToPath3 } from "url";
1437
1505
  import { promisify } from "util";
1438
1506
  var execFileAsync = promisify(execFile);
@@ -1516,7 +1584,7 @@ async function resolveGitRepoRoot(cwd) {
1516
1584
  return (await execGit(cwd, ["rev-parse", "--show-toplevel"], true)).trim();
1517
1585
  }
1518
1586
  function resolveGitScript(name) {
1519
- return join8(dirname3(fileURLToPath3(import.meta.url)), "scripts", name);
1587
+ return join9(dirname3(fileURLToPath3(import.meta.url)), "scripts", name);
1520
1588
  }
1521
1589
  async function runGitScript(name, options) {
1522
1590
  const skipCode = options.skipCode ?? 2;
@@ -1573,65 +1641,6 @@ async function pushCurrentBranch(cwd) {
1573
1641
  });
1574
1642
  }
1575
1643
 
1576
- // src/utils/home-repository.ts
1577
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
1578
- import { join as join9, resolve as resolve3 } from "path";
1579
- var HOME_REPOSITORY_JSON = "/home/repository.json";
1580
- function isSimpleChildDirName(name) {
1581
- if (!name || name === "." || name === "..") return false;
1582
- if (name.includes("/") || name.includes("\\")) return false;
1583
- return true;
1584
- }
1585
- function loadHomeRepositoryEntries() {
1586
- const path = toFsPath(HOME_REPOSITORY_JSON);
1587
- if (!existsSync6(path)) {
1588
- throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${HOME_REPOSITORY_JSON}`);
1589
- }
1590
- let parsed;
1591
- try {
1592
- parsed = JSON.parse(readFileSync5(path, "utf8"));
1593
- } catch (err) {
1594
- const detail = err instanceof Error ? err.message : String(err);
1595
- throw new Error(`[apm] \u89E3\u6790 ${HOME_REPOSITORY_JSON} \u5931\u8D25: ${detail}`);
1596
- }
1597
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1598
- throw new Error(
1599
- `[apm] ${HOME_REPOSITORY_JSON} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
1600
- );
1601
- }
1602
- const entries = [];
1603
- for (const [rawKey, rawVal] of Object.entries(
1604
- parsed
1605
- )) {
1606
- const pathKey = rawKey.trim();
1607
- if (!isSimpleChildDirName(pathKey)) {
1608
- 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`
1610
- );
1611
- }
1612
- if (typeof rawVal !== "string" || !rawVal.trim()) {
1613
- throw new Error(
1614
- `[apm] ${HOME_REPOSITORY_JSON}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
1615
- );
1616
- }
1617
- entries.push({ path: pathKey, remoteUrl: rawVal.trim() });
1618
- }
1619
- entries.sort((a, b) => a.path.localeCompare(b.path));
1620
- if (entries.length === 0) {
1621
- throw new Error(`[apm] ${HOME_REPOSITORY_JSON} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
1622
- }
1623
- return entries;
1624
- }
1625
- function resolveHomeRepositoryAbsolutePaths(workdirInput, entries = loadHomeRepositoryEntries()) {
1626
- const workdir = resolveWorkdirPath(workdirInput);
1627
- return entries.map((entry) => resolve3(join9(workdir, entry.path)));
1628
- }
1629
- function tryLoadHomeRepositoryEntries() {
1630
- const path = toFsPath(HOME_REPOSITORY_JSON);
1631
- if (!existsSync6(path)) return null;
1632
- return loadHomeRepositoryEntries();
1633
- }
1634
-
1635
1644
  // src/utils/workspace-repos.ts
1636
1645
  import {
1637
1646
  existsSync as existsSync7,
@@ -1746,14 +1755,19 @@ function formatRepoLabel(workdir, repoAbs) {
1746
1755
 
1747
1756
  // src/commands/branch.ts
1748
1757
  var TASK_BRANCH_PREFIX = "feat/task-";
1749
- async function syncTaskRepos(workdir, message) {
1758
+ async function syncTaskRepos(workdir, options = {}) {
1759
+ const opts = typeof options === "string" ? { message: options } : options;
1750
1760
  const workdirPath = resolveWorkdirPath(workdir);
1751
- const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
1761
+ const repoRoots = resolveTaskBranchRepoRoots(
1762
+ workdirPath,
1763
+ opts.reposManifest
1764
+ );
1752
1765
  let committed = 0;
1753
1766
  let commitSkipped = 0;
1754
1767
  let pushed = 0;
1755
1768
  let pushSkipped = 0;
1756
1769
  const failed = [];
1770
+ const message = opts.message;
1757
1771
  for (const gitRoot of repoRoots) {
1758
1772
  const label = formatRepoLabel(workdirPath, gitRoot);
1759
1773
  try {
@@ -1811,17 +1825,24 @@ async function ensureTaskFeatureBranch(branch, projectId, options) {
1811
1825
  await ensureFeatureBranch(gitRoot, branch, projectId);
1812
1826
  return branch;
1813
1827
  }
1814
- function resolveTaskBranchRepoRoots(workdirPath) {
1815
- const entries = tryLoadHomeRepositoryEntries();
1816
- if (entries) {
1817
- const roots = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
1828
+ function resolveTaskBranchRepoRoots(workdirPath, reposManifest) {
1829
+ const manifestPath2 = resolveReposManifestPath(reposManifest);
1830
+ if (manifestPath2) {
1831
+ const entries = loadHomeRepositoryEntries(manifestPath2);
1832
+ const roots2 = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
1818
1833
  console.log(
1819
- `[apm] \u8BFB\u53D6 ${HOME_REPOSITORY_JSON} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
1834
+ `[apm] \u8BFB\u53D6\u5916\u90E8\u591A\u4ED3\u6E05\u5355 ${manifestPath2} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
1820
1835
  );
1821
- return roots;
1836
+ return roots2;
1822
1837
  }
1823
- const manifest = loadWorkspaceReposCache(workdirPath);
1824
- return resolveWorkspaceRepoAbsolutePaths(manifest);
1838
+ const workspace = loadWorkspaceReposCache(workdirPath);
1839
+ const roots = resolveWorkspaceRepoAbsolutePaths(workspace);
1840
+ console.log(
1841
+ `[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 repos=${workspace.repos.map(
1842
+ (r) => r.remoteUrl ? `${r.path}\u2192${r.remoteUrl}` : r.path
1843
+ ).join(", ")}`
1844
+ );
1845
+ return roots;
1825
1846
  }
1826
1847
  async function runTaskBranch(taskId, projectId, options = {}) {
1827
1848
  const trimmedTaskId = taskId.trim();
@@ -1837,7 +1858,10 @@ async function runTaskBranch(taskId, projectId, options = {}) {
1837
1858
  }
1838
1859
  const cwd = options.cwd ?? process.cwd();
1839
1860
  const workdirPath = resolveWorkdirPath(cwd);
1840
- const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
1861
+ const repoRoots = resolveTaskBranchRepoRoots(
1862
+ workdirPath,
1863
+ options.reposManifest
1864
+ );
1841
1865
  if (repoRoots.length === 0) {
1842
1866
  throw new Error("[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7279\u6027\u5206\u652F");
1843
1867
  }
@@ -1876,19 +1900,18 @@ async function runTaskBranch(taskId, projectId, options = {}) {
1876
1900
  // src/commands/ensure-task-branch.ts
1877
1901
  async function runEnsureTaskBranchCli(options = {}) {
1878
1902
  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() || "";
1903
+ const taskId = options.taskId?.trim() || "";
1904
+ const projectId = options.projectId?.trim() || "";
1881
1905
  if (!taskId) {
1882
- throw new Error(
1883
- "[apm] \u7F3A\u5C11 taskId\uFF1A\u8BF7\u4F20\u5165\u53C2\u6570\u6216\u8BBE\u7F6E APM_TASK_ID"
1884
- );
1906
+ throw new Error("[apm] \u7F3A\u5C11 taskId\uFF1A\u8BF7\u4F20\u5165\u53C2\u6570");
1885
1907
  }
1886
1908
  if (!projectId) {
1887
- throw new Error(
1888
- "[apm] \u7F3A\u5C11 projectId\uFF1A\u8BF7\u4F20\u5165\u53C2\u6570\u6216\u8BBE\u7F6E APM_PROJECT_ID"
1889
- );
1909
+ throw new Error("[apm] \u7F3A\u5C11 projectId\uFF1A\u8BF7\u4F20\u5165\u53C2\u6570");
1890
1910
  }
1891
- const result = await runTaskBranch(taskId, projectId, { cwd: workdir });
1911
+ const result = await runTaskBranch(taskId, projectId, {
1912
+ cwd: workdir,
1913
+ reposManifest: resolveCliReposManifestPath(options.reposManifest)
1914
+ });
1892
1915
  console.log(
1893
1916
  `[apm] \u7279\u6027\u5206\u652F\u5DF2\u5C31\u7EEA\uFF08\u5168\u90E8 ${result.repos.length} \u4E2A\u4ED3\uFF09 branch=${result.branch} projectId=${result.projectId}`
1894
1917
  );
@@ -1959,7 +1982,10 @@ async function runCommitTaskReposCli(options = {}) {
1959
1982
  if (message) {
1960
1983
  console.log(`[apm] commit message: ${message}`);
1961
1984
  }
1962
- const result = await syncTaskRepos(workdir, message);
1985
+ const result = await syncTaskRepos(workdir, {
1986
+ message,
1987
+ reposManifest: resolveCliReposManifestPath(options.reposManifest)
1988
+ });
1963
1989
  console.log(
1964
1990
  `[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
1991
  );
@@ -2007,30 +2033,35 @@ function buildProgram() {
2007
2033
  await runUpdateSkills();
2008
2034
  });
2009
2035
  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(
2036
+ "\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"
2037
+ ).argument("<taskId>", "\u4EFB\u52A1 ID\uFF08\u521B\u5EFA/\u5207\u6362\u5206\u652F\uFF09").argument(
2038
+ "<projectId>",
2039
+ "\u9879\u76EE ID\uFF08\u4EC5\u521B\u5EFA\u65F6\u7528\uFF1B\u89C4\u8303\u4E0E\u9879\u76EE\u5206\u652F\u540C\u540D\uFF09"
2040
+ ).option("--cwd <dir>", "\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").option(
2041
+ "--repos <file>",
2042
+ "\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"
2043
+ ).action(
2018
2044
  async (taskId, projectId, opts) => {
2019
2045
  await runEnsureTaskBranchCli({
2020
2046
  taskId,
2021
2047
  projectId,
2022
- cwd: opts.cwd
2048
+ cwd: opts.cwd,
2049
+ reposManifest: opts.repos
2023
2050
  });
2024
2051
  }
2025
2052
  );
2026
2053
  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"
2054
+ "\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
2055
  ).option("--cwd <dir>", "\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").option(
2056
+ "--repos <file>",
2057
+ "\u591A\u4ED3\u6E05\u5355 JSON\uFF1B\u7701\u7565\u65F6\u8BFB APM_REPOS_MANIFEST\uFF0C\u518D\u9ED8\u8BA4 /home/repository.json"
2058
+ ).option(
2029
2059
  "-m, --message <msg>",
2030
2060
  "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
2061
  ).action(async (opts) => {
2032
2062
  await runCommitTaskReposCli({
2033
2063
  cwd: opts.cwd,
2064
+ reposManifest: opts.repos,
2034
2065
  message: opts.message
2035
2066
  });
2036
2067
  });
@@ -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,37 @@ 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";
522
521
  function isSimpleChildDirName(name) {
523
522
  if (!name || name === "." || name === "..") return false;
524
523
  if (name.includes("/") || name.includes("\\")) return false;
525
524
  return true;
526
525
  }
527
- function loadHomeRepositoryEntries() {
528
- const path = toFsPath(HOME_REPOSITORY_JSON);
526
+ function resolveReposManifestPath(explicit) {
527
+ const fromOpt = explicit?.trim() || "";
528
+ if (fromOpt) return fromOpt;
529
+ const fromEnv = process.env.APM_REPOS_MANIFEST?.trim() || "";
530
+ if (fromEnv) return fromEnv;
531
+ return null;
532
+ }
533
+ function loadHomeRepositoryEntries(manifestPath2) {
534
+ const label = manifestPath2.trim();
535
+ if (!label) {
536
+ throw new Error("[apm] \u591A\u4ED3\u6E05\u5355\u8DEF\u5F84\u4E3A\u7A7A");
537
+ }
538
+ const path = toFsPath(label);
529
539
  if (!existsSync2(path)) {
530
- throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${HOME_REPOSITORY_JSON}`);
540
+ throw new Error(`[apm] \u672A\u627E\u5230\u591A\u4ED3\u6E05\u5355 ${label}`);
531
541
  }
532
542
  let parsed;
533
543
  try {
534
544
  parsed = JSON.parse(readFileSync2(path, "utf8"));
535
545
  } catch (err) {
536
546
  const detail = err instanceof Error ? err.message : String(err);
537
- throw new Error(`[apm] \u89E3\u6790 ${HOME_REPOSITORY_JSON} \u5931\u8D25: ${detail}`);
547
+ throw new Error(`[apm] \u89E3\u6790 ${label} \u5931\u8D25: ${detail}`);
538
548
  }
539
549
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
540
550
  throw new Error(
541
- `[apm] ${HOME_REPOSITORY_JSON} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
551
+ `[apm] ${label} \u987B\u4E3A\u5BF9\u8C61\uFF1Akey=\u4E0B\u7EA7\u76EE\u5F55\uFF0Cvalue=git \u5730\u5740`
542
552
  );
543
553
  }
544
554
  const entries = [];
@@ -548,31 +558,26 @@ function loadHomeRepositoryEntries() {
548
558
  const pathKey = rawKey.trim();
549
559
  if (!isSimpleChildDirName(pathKey)) {
550
560
  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`
561
+ `[apm] ${label} \u975E\u6CD5\u76EE\u5F55 key: ${JSON.stringify(rawKey)}\uFF08\u987B\u4E3A\u5F53\u524D\u4E0B\u7EA7\u76EE\u5F55\u540D\uFF09`
552
562
  );
553
563
  }
554
564
  if (typeof rawVal !== "string" || !rawVal.trim()) {
555
565
  throw new Error(
556
- `[apm] ${HOME_REPOSITORY_JSON}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
566
+ `[apm] ${label}[${pathKey}] \u987B\u4E3A\u975E\u7A7A git \u5730\u5740\u5B57\u7B26\u4E32`
557
567
  );
558
568
  }
559
569
  entries.push({ path: pathKey, remoteUrl: rawVal.trim() });
560
570
  }
561
571
  entries.sort((a, b) => a.path.localeCompare(b.path));
562
572
  if (entries.length === 0) {
563
- throw new Error(`[apm] ${HOME_REPOSITORY_JSON} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
573
+ throw new Error(`[apm] ${label} \u4E3A\u7A7A\uFF0C\u65E0\u4ED3\u5E93\u53EF\u786E\u8BA4`);
564
574
  }
565
575
  return entries;
566
576
  }
567
- function resolveHomeRepositoryAbsolutePaths(workdirInput, entries = loadHomeRepositoryEntries()) {
577
+ function resolveHomeRepositoryAbsolutePaths(workdirInput, entries) {
568
578
  const workdir = resolveWorkdirPath(workdirInput);
569
579
  return entries.map((entry) => resolve3(join4(workdir, entry.path)));
570
580
  }
571
- function tryLoadHomeRepositoryEntries() {
572
- const path = toFsPath(HOME_REPOSITORY_JSON);
573
- if (!existsSync2(path)) return null;
574
- return loadHomeRepositoryEntries();
575
- }
576
581
 
577
582
  // src/utils/workspace-repos.ts
578
583
  import {
@@ -857,21 +862,26 @@ function formatRepoLabel(workdir, repoAbs) {
857
862
 
858
863
  // src/commands/branch.ts
859
864
  var TASK_BRANCH_PREFIX = "feat/task-";
860
- async function commitWorkspaceReposIfDirty(workdir, message) {
861
- const result = await commitTaskReposIfDirty(workdir, message);
865
+ async function commitWorkspaceReposIfDirty(workdir, message, reposManifest) {
866
+ const result = await commitTaskReposIfDirty(workdir, message, reposManifest);
862
867
  return result.committed;
863
868
  }
864
- async function commitTaskReposIfDirty(workdir, message) {
865
- return syncTaskRepos(workdir, message);
869
+ async function commitTaskReposIfDirty(workdir, message, reposManifest) {
870
+ return syncTaskRepos(workdir, { message, reposManifest });
866
871
  }
867
- async function syncTaskRepos(workdir, message) {
872
+ async function syncTaskRepos(workdir, options = {}) {
873
+ const opts = typeof options === "string" ? { message: options } : options;
868
874
  const workdirPath = resolveWorkdirPath(workdir);
869
- const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
875
+ const repoRoots = resolveTaskBranchRepoRoots(
876
+ workdirPath,
877
+ opts.reposManifest
878
+ );
870
879
  let committed = 0;
871
880
  let commitSkipped = 0;
872
881
  let pushed = 0;
873
882
  let pushSkipped = 0;
874
883
  const failed = [];
884
+ const message = opts.message;
875
885
  for (const gitRoot of repoRoots) {
876
886
  const label = formatRepoLabel(workdirPath, gitRoot);
877
887
  try {
@@ -960,17 +970,24 @@ async function resolveProjectIdForTask(taskId) {
960
970
  }
961
971
  return projectId;
962
972
  }
963
- function resolveTaskBranchRepoRoots(workdirPath) {
964
- const entries = tryLoadHomeRepositoryEntries();
965
- if (entries) {
966
- const roots = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
973
+ function resolveTaskBranchRepoRoots(workdirPath, reposManifest) {
974
+ const manifestPath2 = resolveReposManifestPath(reposManifest);
975
+ if (manifestPath2) {
976
+ const entries = loadHomeRepositoryEntries(manifestPath2);
977
+ const roots2 = resolveHomeRepositoryAbsolutePaths(workdirPath, entries);
967
978
  console.log(
968
- `[apm] \u8BFB\u53D6 ${HOME_REPOSITORY_JSON} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
979
+ `[apm] \u8BFB\u53D6\u5916\u90E8\u591A\u4ED3\u6E05\u5355 ${manifestPath2} repos=${entries.map((e) => `${e.path}\u2192${e.remoteUrl}`).join(", ")}`
969
980
  );
970
- return roots;
981
+ return roots2;
971
982
  }
972
- const manifest = loadWorkspaceReposCache(workdirPath);
973
- return resolveWorkspaceRepoAbsolutePaths(manifest);
983
+ const workspace = loadWorkspaceReposCache(workdirPath);
984
+ const roots = resolveWorkspaceRepoAbsolutePaths(workspace);
985
+ console.log(
986
+ `[apm] \u8BFB\u53D6\u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355 repos=${workspace.repos.map(
987
+ (r) => r.remoteUrl ? `${r.path}\u2192${r.remoteUrl}` : r.path
988
+ ).join(", ")}`
989
+ );
990
+ return roots;
974
991
  }
975
992
  async function runTaskBranch(taskId, projectId, options = {}) {
976
993
  const trimmedTaskId = taskId.trim();
@@ -986,7 +1003,10 @@ async function runTaskBranch(taskId, projectId, options = {}) {
986
1003
  }
987
1004
  const cwd = options.cwd ?? process.cwd();
988
1005
  const workdirPath = resolveWorkdirPath(cwd);
989
- const repoRoots = resolveTaskBranchRepoRoots(workdirPath);
1006
+ const repoRoots = resolveTaskBranchRepoRoots(
1007
+ workdirPath,
1008
+ options.reposManifest
1009
+ );
990
1010
  if (repoRoots.length === 0) {
991
1011
  throw new Error("[apm] \u5DE5\u4F5C\u533A\u4ED3\u5E93\u6E05\u5355\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7279\u6027\u5206\u652F");
992
1012
  }
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.24",
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
- }