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 +123 -92
- package/dist/scripts/ensure_feature_branch.sh +6 -4
- package/dist/worker-script.js +50 -30
- 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
|
@@ -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
|
|
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
|
|
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,
|
|
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(
|
|
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
|
|
1816
|
-
if (
|
|
1817
|
-
const
|
|
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 ${
|
|
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
|
|
1836
|
+
return roots2;
|
|
1822
1837
|
}
|
|
1823
|
-
const
|
|
1824
|
-
|
|
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(
|
|
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() ||
|
|
1880
|
-
const projectId = options.projectId?.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, {
|
|
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,
|
|
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
|
-
"\
|
|
2011
|
-
).argument(
|
|
2012
|
-
"
|
|
2013
|
-
"\
|
|
2014
|
-
).
|
|
2015
|
-
"
|
|
2016
|
-
"\
|
|
2017
|
-
).
|
|
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
|
|
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}"
|
package/dist/worker-script.js
CHANGED
|
@@ -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
|
|
528
|
-
const
|
|
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 ${
|
|
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 ${
|
|
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] ${
|
|
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] ${
|
|
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] ${
|
|
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] ${
|
|
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
|
|
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,
|
|
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(
|
|
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
|
|
965
|
-
if (
|
|
966
|
-
const
|
|
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 ${
|
|
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
|
|
981
|
+
return roots2;
|
|
971
982
|
}
|
|
972
|
-
const
|
|
973
|
-
|
|
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(
|
|
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
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
|
-
}
|