ai-project-manage-cli 6.0.56 → 6.0.58
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/README.md
CHANGED
|
@@ -27,8 +27,12 @@ apm init [--name=我的项目]
|
|
|
27
27
|
```bash
|
|
28
28
|
apm pull <sessionId>
|
|
29
29
|
apm sync-document <sessionId> --file PRD
|
|
30
|
+
apm sync-project-documents
|
|
31
|
+
apm sync-project-documents --push
|
|
30
32
|
apm update-skills
|
|
31
33
|
apm branch <sessionId>
|
|
32
34
|
```
|
|
33
35
|
|
|
36
|
+
`apm pull` 会自动将平台登记的**仓库项目文档**同步到 `.apm/project/`(含 `manifest.json`)。Agent 修改 `.apm/project/` 下文件后,`apm connect` 处理完消息会自动推回平台。
|
|
37
|
+
|
|
34
38
|
详见仓库根目录 [docs/CLI.md](../../docs/CLI.md)。
|
package/dist/index.js
CHANGED
|
@@ -457,6 +457,30 @@ var requestConfig = {
|
|
|
457
457
|
createPullRequest: defineEndpoint({
|
|
458
458
|
method: "POST",
|
|
459
459
|
path: "/cli/pull-requests"
|
|
460
|
+
}),
|
|
461
|
+
getRepositoryProjectDocumentManifest: defineEndpoint({
|
|
462
|
+
method: "GET",
|
|
463
|
+
path: "/cli/repository-project-documents/manifest"
|
|
464
|
+
}),
|
|
465
|
+
listRepositoryProjectDocuments: defineEndpoint({
|
|
466
|
+
method: "GET",
|
|
467
|
+
path: "/cli/repository-project-documents"
|
|
468
|
+
}),
|
|
469
|
+
upsertRepositoryProjectDocument: defineEndpoint({
|
|
470
|
+
method: "PUT",
|
|
471
|
+
path: "/cli/repository-project-documents/upsert"
|
|
472
|
+
}),
|
|
473
|
+
removeRepositoryProjectDocument: defineEndpoint({
|
|
474
|
+
method: "DELETE",
|
|
475
|
+
path: "/cli/repository-project-documents"
|
|
476
|
+
}),
|
|
477
|
+
updateCoordinatorDeploymentStatus: defineEndpoint({
|
|
478
|
+
method: "PUT",
|
|
479
|
+
path: "/cli/coordinator-deployments/status"
|
|
480
|
+
}),
|
|
481
|
+
completeCoordinatorDeployment: defineEndpoint({
|
|
482
|
+
method: "PUT",
|
|
483
|
+
path: "/cli/coordinator-deployments/complete"
|
|
460
484
|
})
|
|
461
485
|
}
|
|
462
486
|
};
|
|
@@ -1086,8 +1110,8 @@ async function runCleanBranches(options = {}) {
|
|
|
1086
1110
|
}
|
|
1087
1111
|
|
|
1088
1112
|
// src/commands/pull.ts
|
|
1089
|
-
import { writeFileSync as
|
|
1090
|
-
import { join as
|
|
1113
|
+
import { writeFileSync as writeFileSync9 } from "fs";
|
|
1114
|
+
import { join as join9 } from "path";
|
|
1091
1115
|
import { stringify as yamlStringify } from "yaml";
|
|
1092
1116
|
|
|
1093
1117
|
// src/session-messages-xml.ts
|
|
@@ -1383,6 +1407,218 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
1383
1407
|
return { written, skipped, removed, repositoryId };
|
|
1384
1408
|
}
|
|
1385
1409
|
|
|
1410
|
+
// src/repository-project-documents-sync.ts
|
|
1411
|
+
import {
|
|
1412
|
+
existsSync as existsSync6,
|
|
1413
|
+
readdirSync as readdirSync3,
|
|
1414
|
+
readFileSync as readFileSync6,
|
|
1415
|
+
rmSync as rmSync3,
|
|
1416
|
+
writeFileSync as writeFileSync8
|
|
1417
|
+
} from "fs";
|
|
1418
|
+
import { createHash } from "crypto";
|
|
1419
|
+
import { dirname as dirname2, join as join8, relative, sep } from "path";
|
|
1420
|
+
var MANIFEST_FILE3 = "manifest.json";
|
|
1421
|
+
function projectDocumentsDir(apmRoot) {
|
|
1422
|
+
return join8(apmRoot ?? workspaceApmDir(), "project");
|
|
1423
|
+
}
|
|
1424
|
+
function projectDocumentLocalPath(apmRoot, documentPath) {
|
|
1425
|
+
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
1426
|
+
return join8(projectDocumentsDir(apmRoot), ...normalized.split("/"));
|
|
1427
|
+
}
|
|
1428
|
+
function normalizeLocalDocumentPath(path10) {
|
|
1429
|
+
const trimmed = path10.trim().replace(/\\/g, "/");
|
|
1430
|
+
if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
|
|
1431
|
+
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
|
|
1432
|
+
}
|
|
1433
|
+
const segments = trimmed.split("/").filter(Boolean);
|
|
1434
|
+
if (segments.some((segment) => segment === ".." || segment === ".")) {
|
|
1435
|
+
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
|
|
1436
|
+
}
|
|
1437
|
+
return segments.join("/");
|
|
1438
|
+
}
|
|
1439
|
+
function hashLocalFileContent(content) {
|
|
1440
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
1441
|
+
}
|
|
1442
|
+
function readLocalManifest(apmRoot) {
|
|
1443
|
+
const manifestPath2 = join8(projectDocumentsDir(apmRoot), MANIFEST_FILE3);
|
|
1444
|
+
if (!existsSync6(manifestPath2)) {
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
try {
|
|
1448
|
+
return JSON.parse(
|
|
1449
|
+
readFileSync6(manifestPath2, "utf8")
|
|
1450
|
+
);
|
|
1451
|
+
} catch {
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
function listLocalDocumentPaths(apmRoot) {
|
|
1456
|
+
const root = projectDocumentsDir(apmRoot);
|
|
1457
|
+
if (!existsSync6(root)) {
|
|
1458
|
+
return [];
|
|
1459
|
+
}
|
|
1460
|
+
const paths = [];
|
|
1461
|
+
const walk = (dir) => {
|
|
1462
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
1463
|
+
const abs = join8(dir, entry.name);
|
|
1464
|
+
if (entry.isDirectory()) {
|
|
1465
|
+
walk(abs);
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
if (entry.isFile() && entry.name === MANIFEST_FILE3) {
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
const rel = relative(root, abs).split(sep).join("/");
|
|
1472
|
+
paths.push(rel);
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
walk(root);
|
|
1476
|
+
return paths.sort();
|
|
1477
|
+
}
|
|
1478
|
+
function diffManifestPaths(remote, local) {
|
|
1479
|
+
const remoteMap = new Map(
|
|
1480
|
+
(remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
1481
|
+
);
|
|
1482
|
+
const localMap = new Map(
|
|
1483
|
+
(local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
1484
|
+
);
|
|
1485
|
+
const download = [];
|
|
1486
|
+
for (const [path10, hash] of remoteMap) {
|
|
1487
|
+
if (localMap.get(path10) !== hash) {
|
|
1488
|
+
download.push(path10);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
const deleteLocal = [];
|
|
1492
|
+
for (const path10 of localMap.keys()) {
|
|
1493
|
+
if (!remoteMap.has(path10)) {
|
|
1494
|
+
deleteLocal.push(path10);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return { download, deleteLocal };
|
|
1498
|
+
}
|
|
1499
|
+
async function syncRepositoryProjectDocumentsPull(workdirPath, apmDir) {
|
|
1500
|
+
const empty = {
|
|
1501
|
+
synced: false,
|
|
1502
|
+
repositoryId: null,
|
|
1503
|
+
downloaded: 0,
|
|
1504
|
+
deleted: 0
|
|
1505
|
+
};
|
|
1506
|
+
const cfg = await tryReadApmConfig();
|
|
1507
|
+
if (!cfg || !resolveApiKey(cfg)) {
|
|
1508
|
+
console.log(
|
|
1509
|
+
"[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
|
|
1510
|
+
);
|
|
1511
|
+
return empty;
|
|
1512
|
+
}
|
|
1513
|
+
const api = createApmApiClient(cfg);
|
|
1514
|
+
const { repositoryId, diagnostic } = await resolveRepositoryIdForSync(
|
|
1515
|
+
api,
|
|
1516
|
+
workdirPath
|
|
1517
|
+
);
|
|
1518
|
+
if (!repositoryId) {
|
|
1519
|
+
console.log(
|
|
1520
|
+
`[apm] \u672A\u80FD\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u3002
|
|
1521
|
+
${diagnostic ?? ""}`
|
|
1522
|
+
);
|
|
1523
|
+
return empty;
|
|
1524
|
+
}
|
|
1525
|
+
const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
|
|
1526
|
+
const projectDir = projectDocumentsDir(targetApmDir);
|
|
1527
|
+
await ensureDirExists(projectDir);
|
|
1528
|
+
const { manifest: remoteManifest } = await api.cli.getRepositoryProjectDocumentManifest({ repositoryId });
|
|
1529
|
+
if (!remoteManifest) {
|
|
1530
|
+
console.log(
|
|
1531
|
+
`[apm] \u4ED3\u5E93 ${repositoryId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`
|
|
1532
|
+
);
|
|
1533
|
+
return { ...empty, repositoryId };
|
|
1534
|
+
}
|
|
1535
|
+
const localManifest = readLocalManifest(targetApmDir);
|
|
1536
|
+
const { download, deleteLocal } = diffManifestPaths(
|
|
1537
|
+
remoteManifest,
|
|
1538
|
+
localManifest
|
|
1539
|
+
);
|
|
1540
|
+
let downloaded = 0;
|
|
1541
|
+
if (download.length > 0) {
|
|
1542
|
+
const { list } = await api.cli.listRepositoryProjectDocuments({
|
|
1543
|
+
repositoryId,
|
|
1544
|
+
paths: download.join(",")
|
|
1545
|
+
});
|
|
1546
|
+
for (const doc of list) {
|
|
1547
|
+
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, doc.path));
|
|
1548
|
+
await ensureDirExists(dirname2(absPath));
|
|
1549
|
+
writeFileSync8(absPath, doc.content, "utf8");
|
|
1550
|
+
downloaded += 1;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
let deleted = 0;
|
|
1554
|
+
for (const path10 of deleteLocal) {
|
|
1555
|
+
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
|
|
1556
|
+
if (existsSync6(absPath)) {
|
|
1557
|
+
rmSync3(absPath, { force: true });
|
|
1558
|
+
deleted += 1;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
writeFileSync8(
|
|
1562
|
+
toFsPath(join8(projectDir, MANIFEST_FILE3)),
|
|
1563
|
+
`${JSON.stringify(remoteManifest, null, 2)}
|
|
1564
|
+
`,
|
|
1565
|
+
"utf8"
|
|
1566
|
+
);
|
|
1567
|
+
console.log(
|
|
1568
|
+
`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted}`
|
|
1569
|
+
);
|
|
1570
|
+
return {
|
|
1571
|
+
synced: true,
|
|
1572
|
+
repositoryId,
|
|
1573
|
+
downloaded,
|
|
1574
|
+
deleted
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
|
|
1578
|
+
const api = createApmApiClient(cfg);
|
|
1579
|
+
const { repositoryId } = await resolveRepositoryIdForSync(api, workdirPath);
|
|
1580
|
+
if (!repositoryId) {
|
|
1581
|
+
return 0;
|
|
1582
|
+
}
|
|
1583
|
+
const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
|
|
1584
|
+
const localPaths = listLocalDocumentPaths(targetApmDir);
|
|
1585
|
+
if (localPaths.length === 0) {
|
|
1586
|
+
console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
1587
|
+
return 0;
|
|
1588
|
+
}
|
|
1589
|
+
const remoteManifest = (await api.cli.getRepositoryProjectDocumentManifest({ repositoryId })).manifest ?? null;
|
|
1590
|
+
const remoteHashByPath = new Map(
|
|
1591
|
+
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
1592
|
+
);
|
|
1593
|
+
const remoteDescriptionByPath = new Map(
|
|
1594
|
+
(remoteManifest?.documents ?? []).map((doc) => [
|
|
1595
|
+
doc.path,
|
|
1596
|
+
doc.description
|
|
1597
|
+
])
|
|
1598
|
+
);
|
|
1599
|
+
let synced = 0;
|
|
1600
|
+
for (const path10 of localPaths) {
|
|
1601
|
+
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
|
|
1602
|
+
const content = readFileSync6(absPath, "utf8");
|
|
1603
|
+
const contentHash = hashLocalFileContent(content);
|
|
1604
|
+
if (remoteHashByPath.get(path10) === contentHash) {
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
await api.cli.upsertRepositoryProjectDocument({
|
|
1608
|
+
repositoryId,
|
|
1609
|
+
path: path10,
|
|
1610
|
+
content,
|
|
1611
|
+
description: remoteDescriptionByPath.get(path10) ?? void 0
|
|
1612
|
+
});
|
|
1613
|
+
synced += 1;
|
|
1614
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: ${path10}`);
|
|
1615
|
+
}
|
|
1616
|
+
if (synced === 0) {
|
|
1617
|
+
console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
1618
|
+
}
|
|
1619
|
+
return synced;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1386
1622
|
// src/commands/pull.ts
|
|
1387
1623
|
async function runPull(sessionId, remoteWorkdir) {
|
|
1388
1624
|
const trimmedId = sessionId.trim();
|
|
@@ -1406,20 +1642,20 @@ async function runPull(sessionId, remoteWorkdir) {
|
|
|
1406
1642
|
const dir = sessionDir(trimmedId, apmRoot);
|
|
1407
1643
|
const docsDir = sessionDocsDir(trimmedId, apmRoot);
|
|
1408
1644
|
await ensureDirExists(docsDir);
|
|
1409
|
-
|
|
1645
|
+
writeFileSync9(
|
|
1410
1646
|
sessionRulePath(trimmedId, apmRoot),
|
|
1411
1647
|
detail.description ?? "",
|
|
1412
1648
|
"utf8"
|
|
1413
1649
|
);
|
|
1414
|
-
|
|
1650
|
+
writeFileSync9(
|
|
1415
1651
|
sessionTaskPath(trimmedId, apmRoot),
|
|
1416
1652
|
detail.task.description ?? "",
|
|
1417
1653
|
"utf8"
|
|
1418
1654
|
);
|
|
1419
|
-
|
|
1655
|
+
writeFileSync9(sessionTodoPath(trimmedId, apmRoot), detail.todo ?? "", "utf8");
|
|
1420
1656
|
for (const doc of documents) {
|
|
1421
1657
|
const fileName = documentLocalFileName(doc.name);
|
|
1422
|
-
|
|
1658
|
+
writeFileSync9(join9(docsDir, fileName), doc.content ?? "", "utf8");
|
|
1423
1659
|
}
|
|
1424
1660
|
const sessionYaml = yamlStringify(
|
|
1425
1661
|
{
|
|
@@ -1436,19 +1672,21 @@ async function runPull(sessionId, remoteWorkdir) {
|
|
|
1436
1672
|
},
|
|
1437
1673
|
{ lineWidth: 0 }
|
|
1438
1674
|
);
|
|
1439
|
-
|
|
1675
|
+
writeFileSync9(
|
|
1440
1676
|
sessionYamlPath(trimmedId, apmRoot),
|
|
1441
1677
|
sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
|
|
1442
1678
|
`,
|
|
1443
1679
|
"utf8"
|
|
1444
1680
|
);
|
|
1445
|
-
|
|
1681
|
+
writeFileSync9(
|
|
1446
1682
|
sessionMessagesXmlPath(trimmedId, apmRoot),
|
|
1447
1683
|
formatSessionMessagesXml(trimmedId, messages),
|
|
1448
1684
|
"utf8"
|
|
1449
1685
|
);
|
|
1450
1686
|
await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
|
|
1451
1687
|
await syncPlatformRules(cfg, trimmedId, workdir, apmRoot);
|
|
1688
|
+
await syncRemoteDeploymentConfig(workdir, apmRoot);
|
|
1689
|
+
await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
|
|
1452
1690
|
console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
|
|
1453
1691
|
return dir;
|
|
1454
1692
|
}
|
|
@@ -1457,15 +1695,15 @@ async function runPull(sessionId, remoteWorkdir) {
|
|
|
1457
1695
|
import { spawnSync } from "child_process";
|
|
1458
1696
|
|
|
1459
1697
|
// src/version.ts
|
|
1460
|
-
import { readFileSync as
|
|
1461
|
-
import { dirname as
|
|
1698
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
1699
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
1462
1700
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1463
1701
|
var CLI_PACKAGE_NAME = "ai-project-manage-cli";
|
|
1464
1702
|
function readCliVersion() {
|
|
1465
1703
|
try {
|
|
1466
|
-
const dir =
|
|
1467
|
-
const pkgPath =
|
|
1468
|
-
const pkg = JSON.parse(
|
|
1704
|
+
const dir = dirname3(fileURLToPath2(import.meta.url));
|
|
1705
|
+
const pkgPath = join10(dir, "..", "package.json");
|
|
1706
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
1469
1707
|
return pkg.version ?? "0.0.0";
|
|
1470
1708
|
} catch {
|
|
1471
1709
|
return "0.0.0";
|
|
@@ -1538,12 +1776,12 @@ async function runUpdate() {
|
|
|
1538
1776
|
}
|
|
1539
1777
|
|
|
1540
1778
|
// src/commands/update-skills.ts
|
|
1541
|
-
import { existsSync as
|
|
1542
|
-
import { join as
|
|
1779
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
|
|
1780
|
+
import { join as join11 } from "path";
|
|
1543
1781
|
async function syncWorkspaceSkills(cfg, workdir) {
|
|
1544
1782
|
const apmDir = workspaceApmDir(workdir);
|
|
1545
1783
|
const fsApmDir = toFsPath(apmDir);
|
|
1546
|
-
if (!
|
|
1784
|
+
if (!existsSync7(fsApmDir)) {
|
|
1547
1785
|
throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1548
1786
|
}
|
|
1549
1787
|
const apmStat = statSync3(fsApmDir);
|
|
@@ -1555,12 +1793,12 @@ async function syncWorkspaceSkills(cfg, workdir) {
|
|
|
1555
1793
|
if (syncAgentsGuide(apmDir)) {
|
|
1556
1794
|
console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
|
|
1557
1795
|
}
|
|
1558
|
-
const rulesDir =
|
|
1796
|
+
const rulesDir = join11(apmDir, "rules");
|
|
1559
1797
|
const ruleNames = syncBaseRules(rulesDir);
|
|
1560
1798
|
for (const name of ruleNames) {
|
|
1561
1799
|
console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
|
|
1562
1800
|
}
|
|
1563
|
-
const skillsDir =
|
|
1801
|
+
const skillsDir = join11(apmDir, "skills");
|
|
1564
1802
|
mkdirSync4(toFsPath(skillsDir), { recursive: true });
|
|
1565
1803
|
const baseNames = syncBaseSkills(skillsDir);
|
|
1566
1804
|
for (const name of baseNames) {
|
|
@@ -1587,7 +1825,7 @@ async function syncWorkspaceSkills(cfg, workdir) {
|
|
|
1587
1825
|
}
|
|
1588
1826
|
async function runUpdateSkills() {
|
|
1589
1827
|
const apmDir = workspaceApmDir();
|
|
1590
|
-
if (!
|
|
1828
|
+
if (!existsSync7(apmDir)) {
|
|
1591
1829
|
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1592
1830
|
process.exit(1);
|
|
1593
1831
|
}
|
|
@@ -1600,11 +1838,11 @@ async function runUpdateSkills() {
|
|
|
1600
1838
|
}
|
|
1601
1839
|
|
|
1602
1840
|
// src/commands/sync-deploy-config.ts
|
|
1603
|
-
import { existsSync as
|
|
1841
|
+
import { existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
1604
1842
|
async function runSyncDeployConfig() {
|
|
1605
1843
|
const workdir = resolveWorkdirPath();
|
|
1606
1844
|
const apmDir = workspaceApmDir(workdir);
|
|
1607
|
-
if (!
|
|
1845
|
+
if (!existsSync8(apmDir)) {
|
|
1608
1846
|
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1609
1847
|
process.exit(1);
|
|
1610
1848
|
}
|
|
@@ -1619,18 +1857,33 @@ async function runSyncDeployConfig() {
|
|
|
1619
1857
|
}
|
|
1620
1858
|
}
|
|
1621
1859
|
|
|
1860
|
+
// src/commands/sync-project-documents.ts
|
|
1861
|
+
async function runSyncProjectDocuments(options) {
|
|
1862
|
+
const pull = options?.pull ?? !options?.push;
|
|
1863
|
+
const push = options?.push ?? false;
|
|
1864
|
+
const workdir = resolveWorkdirPath();
|
|
1865
|
+
const apmRoot = workspaceApmDir(workdir);
|
|
1866
|
+
if (pull) {
|
|
1867
|
+
await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
|
|
1868
|
+
}
|
|
1869
|
+
if (push) {
|
|
1870
|
+
const cfg = await ensureLoggedConfig();
|
|
1871
|
+
await syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1622
1875
|
// src/commands/sync-document.ts
|
|
1623
|
-
import { existsSync as
|
|
1876
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1624
1877
|
import { basename as basename3 } from "path";
|
|
1625
1878
|
|
|
1626
1879
|
// src/commands/sync-session-documents.ts
|
|
1627
|
-
import { existsSync as
|
|
1628
|
-
import { join as
|
|
1880
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
1881
|
+
import { join as join12 } from "path";
|
|
1629
1882
|
function listLocalMarkdownFiles(docsDir) {
|
|
1630
|
-
if (!
|
|
1883
|
+
if (!existsSync9(docsDir)) {
|
|
1631
1884
|
return [];
|
|
1632
1885
|
}
|
|
1633
|
-
return
|
|
1886
|
+
return readdirSync4(docsDir).filter(
|
|
1634
1887
|
(name) => name.toLowerCase().endsWith(".md")
|
|
1635
1888
|
);
|
|
1636
1889
|
}
|
|
@@ -1642,8 +1895,8 @@ function remoteDocumentByLocalName(remoteDocuments, localFileName) {
|
|
|
1642
1895
|
});
|
|
1643
1896
|
}
|
|
1644
1897
|
async function upsertLocalDocumentFile(api, sessionId, docsDir, fileName) {
|
|
1645
|
-
const absPath =
|
|
1646
|
-
const content =
|
|
1898
|
+
const absPath = join12(docsDir, fileName);
|
|
1899
|
+
const content = readFileSync8(absPath, "utf8");
|
|
1647
1900
|
const name = documentPlatformName(absPath);
|
|
1648
1901
|
return api.cli.upsertDocument({
|
|
1649
1902
|
sessionId,
|
|
@@ -1665,8 +1918,8 @@ async function syncSessionDocuments(cfg, sessionId, apmRoot, options) {
|
|
|
1665
1918
|
const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ sessionId: trimmedSessionId });
|
|
1666
1919
|
let synced = 0;
|
|
1667
1920
|
for (const fileName of localFiles) {
|
|
1668
|
-
const absPath =
|
|
1669
|
-
const content =
|
|
1921
|
+
const absPath = join12(docsDir, fileName);
|
|
1922
|
+
const content = readFileSync8(absPath, "utf8");
|
|
1670
1923
|
const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
|
|
1671
1924
|
if (remote && remote.content === content) {
|
|
1672
1925
|
continue;
|
|
@@ -1699,7 +1952,7 @@ async function runSyncDocument(sessionId, options) {
|
|
|
1699
1952
|
process.exit(1);
|
|
1700
1953
|
}
|
|
1701
1954
|
const absPath = resolveSessionDocumentPath(trimmedSessionId, fileArg);
|
|
1702
|
-
if (!
|
|
1955
|
+
if (!existsSync10(absPath)) {
|
|
1703
1956
|
const docsDir2 = sessionDocsDir(trimmedSessionId);
|
|
1704
1957
|
console.error(
|
|
1705
1958
|
`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
|
|
@@ -1853,13 +2106,37 @@ function validateCancel(o) {
|
|
|
1853
2106
|
data: { type: "cancel", messageId: o.messageId.trim() }
|
|
1854
2107
|
};
|
|
1855
2108
|
}
|
|
2109
|
+
function validateDeployPush(o) {
|
|
2110
|
+
if (o.type !== "deploy") {
|
|
2111
|
+
return { ok: false, reason: "\u671F\u671B deploy" };
|
|
2112
|
+
}
|
|
2113
|
+
if (!nonEmptyString(o.deploymentRunId)) {
|
|
2114
|
+
return { ok: false, reason: "deploy \u7F3A\u5C11 deploymentRunId" };
|
|
2115
|
+
}
|
|
2116
|
+
if (!nonEmptyString(o.workdir)) {
|
|
2117
|
+
return { ok: false, reason: "deploy \u7F3A\u5C11 workdir" };
|
|
2118
|
+
}
|
|
2119
|
+
const environment = o.environment;
|
|
2120
|
+
if (environment !== "test" && environment !== "online") {
|
|
2121
|
+
return { ok: false, reason: "deploy.environment \u65E0\u6548" };
|
|
2122
|
+
}
|
|
2123
|
+
return {
|
|
2124
|
+
ok: true,
|
|
2125
|
+
data: {
|
|
2126
|
+
type: "deploy",
|
|
2127
|
+
deploymentRunId: o.deploymentRunId.trim(),
|
|
2128
|
+
workdir: o.workdir.trim(),
|
|
2129
|
+
environment
|
|
2130
|
+
}
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
1856
2133
|
function validateAgentWsMessage(value, kind) {
|
|
1857
2134
|
if (typeof value !== "object" || value === null) {
|
|
1858
2135
|
return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
|
|
1859
2136
|
}
|
|
1860
2137
|
const o = value;
|
|
1861
2138
|
const type = o.type;
|
|
1862
|
-
if (type !== "heartbeat" && type !== "message" && type !== "cancel") {
|
|
2139
|
+
if (type !== "heartbeat" && type !== "message" && type !== "cancel" && type !== "deploy") {
|
|
1863
2140
|
return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
|
|
1864
2141
|
}
|
|
1865
2142
|
if (kind === "heartbeat" || type === "heartbeat") {
|
|
@@ -1868,9 +2145,125 @@ function validateAgentWsMessage(value, kind) {
|
|
|
1868
2145
|
if (type === "cancel") {
|
|
1869
2146
|
return validateCancel(o);
|
|
1870
2147
|
}
|
|
2148
|
+
if (type === "deploy") {
|
|
2149
|
+
return validateDeployPush(o);
|
|
2150
|
+
}
|
|
1871
2151
|
return validateMessagePush(o);
|
|
1872
2152
|
}
|
|
1873
2153
|
|
|
2154
|
+
// src/commands/connect/deploy-run.ts
|
|
2155
|
+
import { spawn } from "node:child_process";
|
|
2156
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
2157
|
+
import { join as join13 } from "node:path";
|
|
2158
|
+
function readDeployConfig(workdir) {
|
|
2159
|
+
const configPath = join13(workspaceApmDir(workdir), "apm.config.json");
|
|
2160
|
+
try {
|
|
2161
|
+
const raw = readFileSync9(configPath, "utf8");
|
|
2162
|
+
const parsed = JSON.parse(raw);
|
|
2163
|
+
return parsed.deploy;
|
|
2164
|
+
} catch {
|
|
2165
|
+
return void 0;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
function resolveDeployCommand(workdir, environment) {
|
|
2169
|
+
const command = readDeployConfig(workdir)?.[environment];
|
|
2170
|
+
if (typeof command === "string" && command.trim()) {
|
|
2171
|
+
return command.trim();
|
|
2172
|
+
}
|
|
2173
|
+
return null;
|
|
2174
|
+
}
|
|
2175
|
+
function missingDeployCommandMessage(environment) {
|
|
2176
|
+
return `deploy.${environment} \u90E8\u7F72\u547D\u4EE4\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u914D\u7F6E`;
|
|
2177
|
+
}
|
|
2178
|
+
function runShellCommand(command, cwd, signal) {
|
|
2179
|
+
return new Promise((resolve5, reject) => {
|
|
2180
|
+
const child = spawn(command, {
|
|
2181
|
+
cwd,
|
|
2182
|
+
shell: true,
|
|
2183
|
+
env: process.env,
|
|
2184
|
+
windowsHide: true
|
|
2185
|
+
});
|
|
2186
|
+
let stdout = "";
|
|
2187
|
+
let stderr = "";
|
|
2188
|
+
const onAbort = () => {
|
|
2189
|
+
child.kill("SIGTERM");
|
|
2190
|
+
};
|
|
2191
|
+
if (signal.aborted) {
|
|
2192
|
+
onAbort();
|
|
2193
|
+
} else {
|
|
2194
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2195
|
+
}
|
|
2196
|
+
child.stdout.on("data", (chunk) => {
|
|
2197
|
+
stdout += String(chunk);
|
|
2198
|
+
});
|
|
2199
|
+
child.stderr.on("data", (chunk) => {
|
|
2200
|
+
stderr += String(chunk);
|
|
2201
|
+
});
|
|
2202
|
+
child.on("error", (error) => {
|
|
2203
|
+
signal.removeEventListener("abort", onAbort);
|
|
2204
|
+
reject(error);
|
|
2205
|
+
});
|
|
2206
|
+
child.on("close", (code) => {
|
|
2207
|
+
signal.removeEventListener("abort", onAbort);
|
|
2208
|
+
const log = [stdout, stderr].filter(Boolean).join("\n");
|
|
2209
|
+
if (code === 0) {
|
|
2210
|
+
resolve5({ log });
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
const error = new Error(
|
|
2214
|
+
`\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
|
|
2215
|
+
);
|
|
2216
|
+
error.log = log;
|
|
2217
|
+
reject(error);
|
|
2218
|
+
});
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
async function handleInboundDeploy(cfg, msg, signal) {
|
|
2222
|
+
const api = createApmApiClient(cfg);
|
|
2223
|
+
const deploymentRunId = msg.deploymentRunId;
|
|
2224
|
+
if (signal.aborted) return;
|
|
2225
|
+
await api.cli.updateCoordinatorDeploymentStatus({
|
|
2226
|
+
id: deploymentRunId,
|
|
2227
|
+
status: "DEPLOYING"
|
|
2228
|
+
});
|
|
2229
|
+
const workdir = requireRemoteWorkdir(msg.workdir);
|
|
2230
|
+
const command = resolveDeployCommand(workdir, msg.environment);
|
|
2231
|
+
if (!command) {
|
|
2232
|
+
const error = missingDeployCommandMessage(msg.environment);
|
|
2233
|
+
console.error(`[apm] ${error}`);
|
|
2234
|
+
await api.cli.completeCoordinatorDeployment({
|
|
2235
|
+
id: deploymentRunId,
|
|
2236
|
+
status: "FAILED",
|
|
2237
|
+
log: error,
|
|
2238
|
+
error
|
|
2239
|
+
});
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
console.log(
|
|
2243
|
+
`[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir}`
|
|
2244
|
+
);
|
|
2245
|
+
console.log(`[apm] deploy command: ${command}`);
|
|
2246
|
+
try {
|
|
2247
|
+
const { log } = await runShellCommand(command, workdir, signal);
|
|
2248
|
+
await api.cli.completeCoordinatorDeployment({
|
|
2249
|
+
id: deploymentRunId,
|
|
2250
|
+
status: "SUCCESS",
|
|
2251
|
+
log
|
|
2252
|
+
});
|
|
2253
|
+
console.log(`[apm] deploy success id=${deploymentRunId}`);
|
|
2254
|
+
} catch (error) {
|
|
2255
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2256
|
+
const log = error && typeof error === "object" && "log" in error ? String(error.log ?? "") : "";
|
|
2257
|
+
await api.cli.completeCoordinatorDeployment({
|
|
2258
|
+
id: deploymentRunId,
|
|
2259
|
+
status: "FAILED",
|
|
2260
|
+
log,
|
|
2261
|
+
error: detail
|
|
2262
|
+
});
|
|
2263
|
+
console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
1874
2267
|
// src/commands/connect/abort-signal-debug.ts
|
|
1875
2268
|
import {
|
|
1876
2269
|
getEventListeners,
|
|
@@ -2100,17 +2493,17 @@ ${JSON.stringify(event, null, 2)}
|
|
|
2100
2493
|
}
|
|
2101
2494
|
|
|
2102
2495
|
// src/commands/connect/agent-session-registry.ts
|
|
2103
|
-
import { existsSync as
|
|
2104
|
-
import { dirname as
|
|
2496
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "node:fs";
|
|
2497
|
+
import { dirname as dirname4, resolve as resolve3 } from "node:path";
|
|
2105
2498
|
function registryPath(workdir, sessionId) {
|
|
2106
2499
|
return resolve3(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
|
|
2107
2500
|
}
|
|
2108
2501
|
function readRegistry(path10) {
|
|
2109
|
-
if (!
|
|
2502
|
+
if (!existsSync11(path10)) {
|
|
2110
2503
|
return {};
|
|
2111
2504
|
}
|
|
2112
2505
|
try {
|
|
2113
|
-
const parsed = JSON.parse(
|
|
2506
|
+
const parsed = JSON.parse(readFileSync10(path10, "utf8"));
|
|
2114
2507
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2115
2508
|
const result = {};
|
|
2116
2509
|
for (const [key, value] of Object.entries(
|
|
@@ -2127,8 +2520,8 @@ function readRegistry(path10) {
|
|
|
2127
2520
|
return {};
|
|
2128
2521
|
}
|
|
2129
2522
|
function writeRegistry(path10, registry) {
|
|
2130
|
-
mkdirSync5(
|
|
2131
|
-
|
|
2523
|
+
mkdirSync5(dirname4(path10), { recursive: true });
|
|
2524
|
+
writeFileSync10(path10, `${JSON.stringify(registry, null, 2)}
|
|
2132
2525
|
`, "utf8");
|
|
2133
2526
|
}
|
|
2134
2527
|
function loadSessionAgentId(workdir, sessionId, user) {
|
|
@@ -2432,20 +2825,20 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
2432
2825
|
}
|
|
2433
2826
|
|
|
2434
2827
|
// src/commands/connect/cli-version-sync.ts
|
|
2435
|
-
import { existsSync as
|
|
2436
|
-
import { join as
|
|
2828
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
2829
|
+
import { join as join14 } from "path";
|
|
2437
2830
|
var CLI_VERSION_FILE = ".cli-version.json";
|
|
2438
2831
|
function manifestPath(apmDir) {
|
|
2439
|
-
return
|
|
2832
|
+
return join14(apmDir, CLI_VERSION_FILE);
|
|
2440
2833
|
}
|
|
2441
2834
|
function loadManifest3(apmDir) {
|
|
2442
2835
|
const path10 = toFsPath(manifestPath(apmDir));
|
|
2443
|
-
if (!
|
|
2836
|
+
if (!existsSync12(path10)) {
|
|
2444
2837
|
return null;
|
|
2445
2838
|
}
|
|
2446
2839
|
try {
|
|
2447
2840
|
const parsed = JSON.parse(
|
|
2448
|
-
|
|
2841
|
+
readFileSync11(path10, "utf8")
|
|
2449
2842
|
);
|
|
2450
2843
|
if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
|
|
2451
2844
|
return parsed;
|
|
@@ -2456,7 +2849,7 @@ function loadManifest3(apmDir) {
|
|
|
2456
2849
|
}
|
|
2457
2850
|
function saveManifest3(apmDir, cliVersion) {
|
|
2458
2851
|
const manifest = { version: 1, cliVersion };
|
|
2459
|
-
|
|
2852
|
+
writeFileSync11(
|
|
2460
2853
|
toFsPath(manifestPath(apmDir)),
|
|
2461
2854
|
`${JSON.stringify(manifest, null, 2)}
|
|
2462
2855
|
`,
|
|
@@ -2633,6 +3026,10 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
2633
3026
|
"sync-documents",
|
|
2634
3027
|
() => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
|
|
2635
3028
|
);
|
|
3029
|
+
await runStep(
|
|
3030
|
+
"sync-project-documents",
|
|
3031
|
+
() => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
|
|
3032
|
+
);
|
|
2636
3033
|
await runStep(
|
|
2637
3034
|
"commit-files",
|
|
2638
3035
|
() => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
|
|
@@ -2770,6 +3167,27 @@ async function runConnect(options) {
|
|
|
2770
3167
|
activeRuns.get(messageId)?.abort();
|
|
2771
3168
|
return;
|
|
2772
3169
|
}
|
|
3170
|
+
if (validated.data.type === "deploy") {
|
|
3171
|
+
const msg2 = validated.data;
|
|
3172
|
+
const perDeployController = new AbortController();
|
|
3173
|
+
const signal2 = AbortSignal.any([
|
|
3174
|
+
shutdownAbort.signal,
|
|
3175
|
+
perDeployController.signal
|
|
3176
|
+
]);
|
|
3177
|
+
const task2 = (async () => {
|
|
3178
|
+
await runSlots.acquire();
|
|
3179
|
+
try {
|
|
3180
|
+
await handleInboundDeploy(cfg, msg2, signal2);
|
|
3181
|
+
} finally {
|
|
3182
|
+
runSlots.release();
|
|
3183
|
+
}
|
|
3184
|
+
})();
|
|
3185
|
+
activeTasks.add(task2);
|
|
3186
|
+
void task2.finally(() => {
|
|
3187
|
+
activeTasks.delete(task2);
|
|
3188
|
+
});
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
2773
3191
|
if (validated.data.type !== "message") {
|
|
2774
3192
|
return;
|
|
2775
3193
|
}
|
|
@@ -2852,19 +3270,19 @@ async function runCreatePr(options) {
|
|
|
2852
3270
|
import path5 from "node:path";
|
|
2853
3271
|
|
|
2854
3272
|
// src/commands/deploy/internal/apm-config.ts
|
|
2855
|
-
import { existsSync as
|
|
3273
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
|
|
2856
3274
|
import { resolve as resolve4 } from "node:path";
|
|
2857
3275
|
function loadApmConfig(options) {
|
|
2858
3276
|
const p = resolve4(
|
|
2859
3277
|
process.cwd(),
|
|
2860
3278
|
options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
|
|
2861
3279
|
);
|
|
2862
|
-
if (!
|
|
3280
|
+
if (!existsSync13(p)) {
|
|
2863
3281
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
2864
3282
|
process.exit(1);
|
|
2865
3283
|
}
|
|
2866
3284
|
try {
|
|
2867
|
-
const raw =
|
|
3285
|
+
const raw = readFileSync12(p, "utf8");
|
|
2868
3286
|
return JSON.parse(raw);
|
|
2869
3287
|
} catch (e) {
|
|
2870
3288
|
console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
|
|
@@ -2986,7 +3404,7 @@ import path4 from "node:path";
|
|
|
2986
3404
|
import Docker from "dockerode";
|
|
2987
3405
|
|
|
2988
3406
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
|
|
2989
|
-
import { existsSync as
|
|
3407
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
|
|
2990
3408
|
import path from "node:path";
|
|
2991
3409
|
function asOptionalTlsBuffer(value) {
|
|
2992
3410
|
if (typeof value !== "string") {
|
|
@@ -2998,8 +3416,8 @@ function asOptionalTlsBuffer(value) {
|
|
|
2998
3416
|
if (normalized === "") {
|
|
2999
3417
|
return void 0;
|
|
3000
3418
|
}
|
|
3001
|
-
if (
|
|
3002
|
-
return
|
|
3419
|
+
if (existsSync14(normalized)) {
|
|
3420
|
+
return readFileSync13(normalized);
|
|
3003
3421
|
}
|
|
3004
3422
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
3005
3423
|
if (looksLikePath) {
|
|
@@ -3209,7 +3627,7 @@ var DockerodeClient = class {
|
|
|
3209
3627
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
3210
3628
|
|
|
3211
3629
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
|
|
3212
|
-
import { existsSync as
|
|
3630
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
|
|
3213
3631
|
import path2 from "node:path";
|
|
3214
3632
|
function stripSurroundingQuotes(value) {
|
|
3215
3633
|
const t = value.trim();
|
|
@@ -3226,10 +3644,10 @@ function loadEnvFromFile(envFilePath) {
|
|
|
3226
3644
|
return {};
|
|
3227
3645
|
}
|
|
3228
3646
|
const targetPath = path2.resolve(envFilePath);
|
|
3229
|
-
if (!
|
|
3647
|
+
if (!existsSync15(targetPath) || !statSync5(targetPath).isFile()) {
|
|
3230
3648
|
return {};
|
|
3231
3649
|
}
|
|
3232
|
-
const raw =
|
|
3650
|
+
const raw = readFileSync14(targetPath, "utf-8");
|
|
3233
3651
|
const result = {};
|
|
3234
3652
|
for (const line of raw.split(/\r?\n/)) {
|
|
3235
3653
|
const normalized = line.trim();
|
|
@@ -3400,12 +3818,12 @@ function dockerPushImage(params, cwd) {
|
|
|
3400
3818
|
}
|
|
3401
3819
|
|
|
3402
3820
|
// src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
|
|
3403
|
-
import { existsSync as
|
|
3821
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
3404
3822
|
import path3 from "node:path";
|
|
3405
3823
|
function resolveDockerBuildPaths(cwd) {
|
|
3406
3824
|
const dockerfilePath = path3.join(cwd, "Dockerfile");
|
|
3407
3825
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
3408
|
-
if (!
|
|
3826
|
+
if (!existsSync16(dockerfilePath)) {
|
|
3409
3827
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
3410
3828
|
}
|
|
3411
3829
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|
|
@@ -4034,8 +4452,13 @@ function buildProgram() {
|
|
|
4034
4452
|
).action(async () => {
|
|
4035
4453
|
await runSyncDeployConfig();
|
|
4036
4454
|
});
|
|
4455
|
+
program.command("sync-project-documents").description(
|
|
4456
|
+
"\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\uFF1A\u9ED8\u8BA4 pull \u5230 .apm/project/\uFF1B\u52A0 --push \u5C06\u672C\u5730\u53D8\u66F4\u63A8\u9001\u5230\u5E73\u53F0"
|
|
4457
|
+
).option("--push", "\u63A8\u9001\u672C\u5730 .apm/project/ \u5230\u5E73\u53F0").option("--pull", "\u4ECE\u5E73\u53F0\u62C9\u53D6\u5230 .apm/project/").action(async (opts) => {
|
|
4458
|
+
await runSyncProjectDocuments(opts);
|
|
4459
|
+
});
|
|
4037
4460
|
program.command("pull").description(
|
|
4038
|
-
"\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\
|
|
4461
|
+
"\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF0C\u5E76\u540C\u6B65\u90E8\u7F72\u914D\u7F6E\u4E0E\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u5230 .apm/project/"
|
|
4039
4462
|
).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
|
|
4040
4463
|
await runPull(sessionId);
|
|
4041
4464
|
});
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
- reply.md:当需要回复消息时需要读取这个文档,记住回复规则
|
|
31
31
|
- write_doc.md:当需要写文档时需要读取这个文档,记住写文档的规则
|
|
32
32
|
|
|
33
|
+
- 仓库项目上下文(`.apm/project/`):
|
|
34
|
+
- 索引: `.apm/project/manifest.json` — 本仓库在平台登记的文档列表
|
|
35
|
+
- 文档文件: `.apm/project/{path}` — 与 manifest 中 path 对应;`apm pull` 自动同步,`apm connect` 消息结束后自动推回平台
|
|
36
|
+
- 任务涉及菜单名、路由、业务术语等时,**先 Read manifest 与相关文档**,禁止猜测路径
|
|
37
|
+
|
|
33
38
|
- 本轮任务需要关注的文档指引(.apm/sessions/<会话 ID>/\*):
|
|
34
39
|
- 本轮会话状态: `session.yaml`,从这里可以看到每个成员的信息,找到可以协助你一起解决问题的人,可以结合 `RULE.md`一起看
|
|
35
40
|
- 群文档: `docs/xxxx.md`,当需要相关上下文可以在这里查找,按需阅读
|
package/template/apm.config.json
CHANGED
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
|
|
53
53
|
- **目标数据库**(库名/实例名、类型如 MySQL;同一变更涉及多库时分别标注)
|
|
54
54
|
- 变更摘要(改了什么表/数据、为什么)
|
|
55
|
-
- 完整 SQL 语句(按执行顺序排列;**每条须放在 ` ```sql `
|
|
55
|
+
- 完整 SQL 语句(按执行顺序排列;**每条须放在 ` ```sql ` 代码块中**,便于复制执行;**禁止**用 `...` 或「省略」占位代替任何片段)
|
|
56
56
|
- 执行环境说明(测试/生产是否一致、是否需人工执行)
|
|
57
|
-
- 回滚方案(如适用;回滚 SQL 同样用 ` ```sql `
|
|
57
|
+
- 回滚方案(如适用;回滚 SQL 同样用 ` ```sql ` 代码块,须完整可执行,禁止省略)
|
|
58
58
|
|
|
59
59
|
示例:
|
|
60
60
|
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
|
|
19
19
|
### 步骤 2:四项检查
|
|
20
20
|
|
|
21
|
-
| 检查项 | 判定
|
|
22
|
-
| -------------- |
|
|
23
|
-
| **白名单对账** | diff 中出现白名单之外的文件,且计划未更新说明 → **不通过**
|
|
24
|
-
| **需求相关性** | 存在与本需求无关的改动(顺手重构、改格式、动了无关逻辑)→ **不通过**
|
|
25
|
-
| **计划落实** | 计划「实现步骤」中的关键点在 diff 中找不到对应实现 → **不通过**
|
|
26
|
-
| **SQL 文档** | **仅后端**:diff 涉及 SQL 改动(DDL/DML、表结构、Mapper/XML 中 SQL 等),但 `docs/SQL.md`
|
|
21
|
+
| 检查项 | 判定 |
|
|
22
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| **白名单对账** | diff 中出现白名单之外的文件,且计划未更新说明 → **不通过** |
|
|
24
|
+
| **需求相关性** | 存在与本需求无关的改动(顺手重构、改格式、动了无关逻辑)→ **不通过** |
|
|
25
|
+
| **计划落实** | 计划「实现步骤」中的关键点在 diff 中找不到对应实现 → **不通过** |
|
|
26
|
+
| **SQL 文档** | **仅后端**:diff 涉及 SQL 改动(DDL/DML、表结构、Mapper/XML 中 SQL 等),但 `docs/SQL.md` 缺失、未标注目标数据库、与改动不一致、或 SQL 代码块含 `...`/「省略」占位 → **不通过** |
|
|
27
27
|
|
|
28
28
|
注意事项:
|
|
29
29
|
|