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 +67 -147
- package/dist/scripts/ensure_feature_branch.sh +6 -4
- package/dist/worker-script.js +41 -33
- package/package.json +1 -1
- package/template/README.md +0 -13
- package/template/hooks/after-agent-commit-repos.cjs +0 -112
- package/template/hooks/before-prompt-prepare.cjs +0 -206
- package/template/hooks.json +0 -18
- package/template/mcp.json +0 -18
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
|
|
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
|
|
1586
|
-
const
|
|
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 ${
|
|
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 ${
|
|
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] ${
|
|
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] ${
|
|
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] ${
|
|
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] ${
|
|
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
|
|
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,
|
|
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(
|
|
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
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
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(
|
|
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() ||
|
|
1880
|
-
const projectId = options.projectId?.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, {
|
|
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
|
|
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 (!
|
|
1833
|
+
if (!existsSync7(path)) return null;
|
|
1922
1834
|
try {
|
|
1923
|
-
const raw =
|
|
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,
|
|
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
|
-
"\
|
|
2011
|
-
).argument(
|
|
2012
|
-
"
|
|
2013
|
-
"\
|
|
2014
|
-
).
|
|
2015
|
-
"
|
|
2016
|
-
"\
|
|
2017
|
-
).
|
|
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
|
|
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}"
|
package/dist/worker-script.js
CHANGED
|
@@ -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
|
|
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
|
|
528
|
-
const
|
|
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 ${
|
|
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 ${
|
|
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] ${
|
|
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] ${
|
|
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] ${
|
|
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] ${
|
|
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
|
|
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,
|
|
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(
|
|
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
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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(
|
|
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
package/template/README.md
DELETED
|
@@ -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
|
-
}
|
package/template/hooks.json
DELETED
|
@@ -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
|
-
}
|