ai-project-manage-cli 6.0.57 → 6.0.59
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 +4 -0
- package/dist/index.js +511 -66
- package/package.json +1 -1
- package/template/AGENTS.md +5 -0
- package/template/apm.config.json +6 -2
- package/template/project/.gitkeep +0 -0
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
|
@@ -149,13 +149,11 @@ var CLI_TEMPLATE_DIR = resolve2(__dirname, "../template");
|
|
|
149
149
|
function workspaceApmDir(cwd = resolveWorkdirPath()) {
|
|
150
150
|
return resolve2(resolve2(cwd), ".apm");
|
|
151
151
|
}
|
|
152
|
-
function
|
|
152
|
+
function isWorkspaceApmInitialized(workdir) {
|
|
153
153
|
const apmDir = workspaceApmDir(workdir);
|
|
154
154
|
const fsApmDir = toFsPath(apmDir);
|
|
155
155
|
if (!existsSync(fsApmDir)) {
|
|
156
|
-
|
|
157
|
-
`\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u4E0B\u672A\u68C0\u6D4B\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5728\u8BE5\u4ED3\u5E93\u6839\u76EE\u5F55\u6267\u884C apm init \u5B8C\u6210\u63A5\u5165\u3002`
|
|
158
|
-
);
|
|
156
|
+
return false;
|
|
159
157
|
}
|
|
160
158
|
const st = statSync(fsApmDir);
|
|
161
159
|
if (!st.isDirectory()) {
|
|
@@ -163,6 +161,7 @@ function assertWorkspaceApmDirExists(workdir) {
|
|
|
163
161
|
`\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u4E0B\u7684 .apm \u4E0D\u662F\u76EE\u5F55\uFF0C\u8BF7\u68C0\u67E5\u672C\u5730\u63A5\u5165\u72B6\u6001\u3002`
|
|
164
162
|
);
|
|
165
163
|
}
|
|
164
|
+
return readdirSync(fsApmDir).length > 0;
|
|
166
165
|
}
|
|
167
166
|
var APM_GITIGNORE_PATTERNS = [
|
|
168
167
|
/^\.apm\/?$/,
|
|
@@ -457,6 +456,30 @@ var requestConfig = {
|
|
|
457
456
|
createPullRequest: defineEndpoint({
|
|
458
457
|
method: "POST",
|
|
459
458
|
path: "/cli/pull-requests"
|
|
459
|
+
}),
|
|
460
|
+
getRepositoryProjectDocumentManifest: defineEndpoint({
|
|
461
|
+
method: "GET",
|
|
462
|
+
path: "/cli/repository-project-documents/manifest"
|
|
463
|
+
}),
|
|
464
|
+
listRepositoryProjectDocuments: defineEndpoint({
|
|
465
|
+
method: "GET",
|
|
466
|
+
path: "/cli/repository-project-documents"
|
|
467
|
+
}),
|
|
468
|
+
upsertRepositoryProjectDocument: defineEndpoint({
|
|
469
|
+
method: "PUT",
|
|
470
|
+
path: "/cli/repository-project-documents/upsert"
|
|
471
|
+
}),
|
|
472
|
+
removeRepositoryProjectDocument: defineEndpoint({
|
|
473
|
+
method: "DELETE",
|
|
474
|
+
path: "/cli/repository-project-documents"
|
|
475
|
+
}),
|
|
476
|
+
updateCoordinatorDeploymentStatus: defineEndpoint({
|
|
477
|
+
method: "PUT",
|
|
478
|
+
path: "/cli/coordinator-deployments/status"
|
|
479
|
+
}),
|
|
480
|
+
completeCoordinatorDeployment: defineEndpoint({
|
|
481
|
+
method: "PUT",
|
|
482
|
+
path: "/cli/coordinator-deployments/complete"
|
|
460
483
|
})
|
|
461
484
|
}
|
|
462
485
|
};
|
|
@@ -628,8 +651,13 @@ async function commitAndPushGitignore(workdir) {
|
|
|
628
651
|
}
|
|
629
652
|
|
|
630
653
|
// src/commands/init.ts
|
|
631
|
-
async function
|
|
632
|
-
|
|
654
|
+
async function ensureWorkspaceInitialized(workdir, options) {
|
|
655
|
+
if (isWorkspaceApmInitialized(workdir)) {
|
|
656
|
+
return { didInit: false };
|
|
657
|
+
}
|
|
658
|
+
console.log(
|
|
659
|
+
`[apm] \u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u672A\u68C0\u6D4B\u5230\u5DF2\u521D\u59CB\u5316\u7684 .apm\uFF0C\u6B63\u5728\u81EA\u52A8\u521D\u59CB\u5316\u2026`
|
|
660
|
+
);
|
|
633
661
|
await ensureWorkspaceApmDirForInit(workdir);
|
|
634
662
|
if (ensureApmGitignoredInRepo(workdir)) {
|
|
635
663
|
console.log("[apm] \u5DF2\u5728 .gitignore \u4E2D\u6DFB\u52A0 **/.apm/**");
|
|
@@ -638,7 +666,7 @@ async function runInit(name) {
|
|
|
638
666
|
const apmDir = workspaceApmDir(workdir);
|
|
639
667
|
await copyTemplateFiles(apmDir, workdir);
|
|
640
668
|
const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
641
|
-
const trimmedName = name?.trim();
|
|
669
|
+
const trimmedName = options?.name?.trim();
|
|
642
670
|
if (trimmedName) {
|
|
643
671
|
const apmConfigPath = toFsPath(join4(apmDir, "apm.config.json"));
|
|
644
672
|
const config = readFileSync3(apmConfigPath, "utf8");
|
|
@@ -652,8 +680,21 @@ async function runInit(name) {
|
|
|
652
680
|
);
|
|
653
681
|
}
|
|
654
682
|
console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
|
|
683
|
+
return { didInit: true, syncResult };
|
|
684
|
+
}
|
|
685
|
+
async function runInit(name) {
|
|
686
|
+
const workdir = resolveWorkdirPath();
|
|
687
|
+
await ensureWorkspaceApmDirForInit(workdir);
|
|
688
|
+
const { didInit, syncResult } = await ensureWorkspaceInitialized(workdir, {
|
|
689
|
+
name
|
|
690
|
+
});
|
|
691
|
+
if (!didInit) {
|
|
692
|
+
throw new Error(
|
|
693
|
+
"[apm] .apm \u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\uFF0C\u8BF7\u5148\u5907\u4EFD\u3001\u6E05\u7A7A\u6216\u5220\u9664\u540E\u518D\u6267\u884C init"
|
|
694
|
+
);
|
|
695
|
+
}
|
|
655
696
|
console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
|
|
656
|
-
if (!syncResult.synced) {
|
|
697
|
+
if (syncResult && !syncResult.synced) {
|
|
657
698
|
console.log(
|
|
658
699
|
"[apm] \u5F53\u524D .apm/apm.config.json \u4E0E .apm/deploy/README.md \u4E3A\u6A21\u677F\u9ED8\u8BA4\u503C\uFF1B\u5B8C\u6210\u5E73\u53F0\u767B\u8BB0\u540E\u6267\u884C apm sync-deploy-config"
|
|
659
700
|
);
|
|
@@ -1086,8 +1127,8 @@ async function runCleanBranches(options = {}) {
|
|
|
1086
1127
|
}
|
|
1087
1128
|
|
|
1088
1129
|
// src/commands/pull.ts
|
|
1089
|
-
import { writeFileSync as
|
|
1090
|
-
import { join as
|
|
1130
|
+
import { writeFileSync as writeFileSync9 } from "fs";
|
|
1131
|
+
import { join as join9 } from "path";
|
|
1091
1132
|
import { stringify as yamlStringify } from "yaml";
|
|
1092
1133
|
|
|
1093
1134
|
// src/session-messages-xml.ts
|
|
@@ -1383,6 +1424,218 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
1383
1424
|
return { written, skipped, removed, repositoryId };
|
|
1384
1425
|
}
|
|
1385
1426
|
|
|
1427
|
+
// src/repository-project-documents-sync.ts
|
|
1428
|
+
import {
|
|
1429
|
+
existsSync as existsSync6,
|
|
1430
|
+
readdirSync as readdirSync3,
|
|
1431
|
+
readFileSync as readFileSync6,
|
|
1432
|
+
rmSync as rmSync3,
|
|
1433
|
+
writeFileSync as writeFileSync8
|
|
1434
|
+
} from "fs";
|
|
1435
|
+
import { createHash } from "crypto";
|
|
1436
|
+
import { dirname as dirname2, join as join8, relative, sep } from "path";
|
|
1437
|
+
var MANIFEST_FILE3 = "manifest.json";
|
|
1438
|
+
function projectDocumentsDir(apmRoot) {
|
|
1439
|
+
return join8(apmRoot ?? workspaceApmDir(), "project");
|
|
1440
|
+
}
|
|
1441
|
+
function projectDocumentLocalPath(apmRoot, documentPath) {
|
|
1442
|
+
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
1443
|
+
return join8(projectDocumentsDir(apmRoot), ...normalized.split("/"));
|
|
1444
|
+
}
|
|
1445
|
+
function normalizeLocalDocumentPath(path10) {
|
|
1446
|
+
const trimmed = path10.trim().replace(/\\/g, "/");
|
|
1447
|
+
if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
|
|
1448
|
+
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
|
|
1449
|
+
}
|
|
1450
|
+
const segments = trimmed.split("/").filter(Boolean);
|
|
1451
|
+
if (segments.some((segment) => segment === ".." || segment === ".")) {
|
|
1452
|
+
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
|
|
1453
|
+
}
|
|
1454
|
+
return segments.join("/");
|
|
1455
|
+
}
|
|
1456
|
+
function hashLocalFileContent(content) {
|
|
1457
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
1458
|
+
}
|
|
1459
|
+
function readLocalManifest(apmRoot) {
|
|
1460
|
+
const manifestPath2 = join8(projectDocumentsDir(apmRoot), MANIFEST_FILE3);
|
|
1461
|
+
if (!existsSync6(manifestPath2)) {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
try {
|
|
1465
|
+
return JSON.parse(
|
|
1466
|
+
readFileSync6(manifestPath2, "utf8")
|
|
1467
|
+
);
|
|
1468
|
+
} catch {
|
|
1469
|
+
return null;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
function listLocalDocumentPaths(apmRoot) {
|
|
1473
|
+
const root = projectDocumentsDir(apmRoot);
|
|
1474
|
+
if (!existsSync6(root)) {
|
|
1475
|
+
return [];
|
|
1476
|
+
}
|
|
1477
|
+
const paths = [];
|
|
1478
|
+
const walk = (dir) => {
|
|
1479
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
1480
|
+
const abs = join8(dir, entry.name);
|
|
1481
|
+
if (entry.isDirectory()) {
|
|
1482
|
+
walk(abs);
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
if (entry.isFile() && entry.name === MANIFEST_FILE3) {
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
const rel = relative(root, abs).split(sep).join("/");
|
|
1489
|
+
paths.push(rel);
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
walk(root);
|
|
1493
|
+
return paths.sort();
|
|
1494
|
+
}
|
|
1495
|
+
function diffManifestPaths(remote, local) {
|
|
1496
|
+
const remoteMap = new Map(
|
|
1497
|
+
(remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
1498
|
+
);
|
|
1499
|
+
const localMap = new Map(
|
|
1500
|
+
(local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
1501
|
+
);
|
|
1502
|
+
const download = [];
|
|
1503
|
+
for (const [path10, hash] of remoteMap) {
|
|
1504
|
+
if (localMap.get(path10) !== hash) {
|
|
1505
|
+
download.push(path10);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
const deleteLocal = [];
|
|
1509
|
+
for (const path10 of localMap.keys()) {
|
|
1510
|
+
if (!remoteMap.has(path10)) {
|
|
1511
|
+
deleteLocal.push(path10);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
return { download, deleteLocal };
|
|
1515
|
+
}
|
|
1516
|
+
async function syncRepositoryProjectDocumentsPull(workdirPath, apmDir) {
|
|
1517
|
+
const empty = {
|
|
1518
|
+
synced: false,
|
|
1519
|
+
repositoryId: null,
|
|
1520
|
+
downloaded: 0,
|
|
1521
|
+
deleted: 0
|
|
1522
|
+
};
|
|
1523
|
+
const cfg = await tryReadApmConfig();
|
|
1524
|
+
if (!cfg || !resolveApiKey(cfg)) {
|
|
1525
|
+
console.log(
|
|
1526
|
+
"[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"
|
|
1527
|
+
);
|
|
1528
|
+
return empty;
|
|
1529
|
+
}
|
|
1530
|
+
const api = createApmApiClient(cfg);
|
|
1531
|
+
const { repositoryId, diagnostic } = await resolveRepositoryIdForSync(
|
|
1532
|
+
api,
|
|
1533
|
+
workdirPath
|
|
1534
|
+
);
|
|
1535
|
+
if (!repositoryId) {
|
|
1536
|
+
console.log(
|
|
1537
|
+
`[apm] \u672A\u80FD\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u3002
|
|
1538
|
+
${diagnostic ?? ""}`
|
|
1539
|
+
);
|
|
1540
|
+
return empty;
|
|
1541
|
+
}
|
|
1542
|
+
const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
|
|
1543
|
+
const projectDir = projectDocumentsDir(targetApmDir);
|
|
1544
|
+
await ensureDirExists(projectDir);
|
|
1545
|
+
const { manifest: remoteManifest } = await api.cli.getRepositoryProjectDocumentManifest({ repositoryId });
|
|
1546
|
+
if (!remoteManifest) {
|
|
1547
|
+
console.log(
|
|
1548
|
+
`[apm] \u4ED3\u5E93 ${repositoryId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`
|
|
1549
|
+
);
|
|
1550
|
+
return { ...empty, repositoryId };
|
|
1551
|
+
}
|
|
1552
|
+
const localManifest = readLocalManifest(targetApmDir);
|
|
1553
|
+
const { download, deleteLocal } = diffManifestPaths(
|
|
1554
|
+
remoteManifest,
|
|
1555
|
+
localManifest
|
|
1556
|
+
);
|
|
1557
|
+
let downloaded = 0;
|
|
1558
|
+
if (download.length > 0) {
|
|
1559
|
+
const { list } = await api.cli.listRepositoryProjectDocuments({
|
|
1560
|
+
repositoryId,
|
|
1561
|
+
paths: download.join(",")
|
|
1562
|
+
});
|
|
1563
|
+
for (const doc of list) {
|
|
1564
|
+
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, doc.path));
|
|
1565
|
+
await ensureDirExists(dirname2(absPath));
|
|
1566
|
+
writeFileSync8(absPath, doc.content, "utf8");
|
|
1567
|
+
downloaded += 1;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
let deleted = 0;
|
|
1571
|
+
for (const path10 of deleteLocal) {
|
|
1572
|
+
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
|
|
1573
|
+
if (existsSync6(absPath)) {
|
|
1574
|
+
rmSync3(absPath, { force: true });
|
|
1575
|
+
deleted += 1;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
writeFileSync8(
|
|
1579
|
+
toFsPath(join8(projectDir, MANIFEST_FILE3)),
|
|
1580
|
+
`${JSON.stringify(remoteManifest, null, 2)}
|
|
1581
|
+
`,
|
|
1582
|
+
"utf8"
|
|
1583
|
+
);
|
|
1584
|
+
console.log(
|
|
1585
|
+
`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted}`
|
|
1586
|
+
);
|
|
1587
|
+
return {
|
|
1588
|
+
synced: true,
|
|
1589
|
+
repositoryId,
|
|
1590
|
+
downloaded,
|
|
1591
|
+
deleted
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
|
|
1595
|
+
const api = createApmApiClient(cfg);
|
|
1596
|
+
const { repositoryId } = await resolveRepositoryIdForSync(api, workdirPath);
|
|
1597
|
+
if (!repositoryId) {
|
|
1598
|
+
return 0;
|
|
1599
|
+
}
|
|
1600
|
+
const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
|
|
1601
|
+
const localPaths = listLocalDocumentPaths(targetApmDir);
|
|
1602
|
+
if (localPaths.length === 0) {
|
|
1603
|
+
console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
1604
|
+
return 0;
|
|
1605
|
+
}
|
|
1606
|
+
const remoteManifest = (await api.cli.getRepositoryProjectDocumentManifest({ repositoryId })).manifest ?? null;
|
|
1607
|
+
const remoteHashByPath = new Map(
|
|
1608
|
+
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
1609
|
+
);
|
|
1610
|
+
const remoteDescriptionByPath = new Map(
|
|
1611
|
+
(remoteManifest?.documents ?? []).map((doc) => [
|
|
1612
|
+
doc.path,
|
|
1613
|
+
doc.description
|
|
1614
|
+
])
|
|
1615
|
+
);
|
|
1616
|
+
let synced = 0;
|
|
1617
|
+
for (const path10 of localPaths) {
|
|
1618
|
+
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
|
|
1619
|
+
const content = readFileSync6(absPath, "utf8");
|
|
1620
|
+
const contentHash = hashLocalFileContent(content);
|
|
1621
|
+
if (remoteHashByPath.get(path10) === contentHash) {
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1624
|
+
await api.cli.upsertRepositoryProjectDocument({
|
|
1625
|
+
repositoryId,
|
|
1626
|
+
path: path10,
|
|
1627
|
+
content,
|
|
1628
|
+
description: remoteDescriptionByPath.get(path10) ?? void 0
|
|
1629
|
+
});
|
|
1630
|
+
synced += 1;
|
|
1631
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: ${path10}`);
|
|
1632
|
+
}
|
|
1633
|
+
if (synced === 0) {
|
|
1634
|
+
console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
1635
|
+
}
|
|
1636
|
+
return synced;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1386
1639
|
// src/commands/pull.ts
|
|
1387
1640
|
async function runPull(sessionId, remoteWorkdir) {
|
|
1388
1641
|
const trimmedId = sessionId.trim();
|
|
@@ -1406,20 +1659,20 @@ async function runPull(sessionId, remoteWorkdir) {
|
|
|
1406
1659
|
const dir = sessionDir(trimmedId, apmRoot);
|
|
1407
1660
|
const docsDir = sessionDocsDir(trimmedId, apmRoot);
|
|
1408
1661
|
await ensureDirExists(docsDir);
|
|
1409
|
-
|
|
1662
|
+
writeFileSync9(
|
|
1410
1663
|
sessionRulePath(trimmedId, apmRoot),
|
|
1411
1664
|
detail.description ?? "",
|
|
1412
1665
|
"utf8"
|
|
1413
1666
|
);
|
|
1414
|
-
|
|
1667
|
+
writeFileSync9(
|
|
1415
1668
|
sessionTaskPath(trimmedId, apmRoot),
|
|
1416
1669
|
detail.task.description ?? "",
|
|
1417
1670
|
"utf8"
|
|
1418
1671
|
);
|
|
1419
|
-
|
|
1672
|
+
writeFileSync9(sessionTodoPath(trimmedId, apmRoot), detail.todo ?? "", "utf8");
|
|
1420
1673
|
for (const doc of documents) {
|
|
1421
1674
|
const fileName = documentLocalFileName(doc.name);
|
|
1422
|
-
|
|
1675
|
+
writeFileSync9(join9(docsDir, fileName), doc.content ?? "", "utf8");
|
|
1423
1676
|
}
|
|
1424
1677
|
const sessionYaml = yamlStringify(
|
|
1425
1678
|
{
|
|
@@ -1436,19 +1689,21 @@ async function runPull(sessionId, remoteWorkdir) {
|
|
|
1436
1689
|
},
|
|
1437
1690
|
{ lineWidth: 0 }
|
|
1438
1691
|
);
|
|
1439
|
-
|
|
1692
|
+
writeFileSync9(
|
|
1440
1693
|
sessionYamlPath(trimmedId, apmRoot),
|
|
1441
1694
|
sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
|
|
1442
1695
|
`,
|
|
1443
1696
|
"utf8"
|
|
1444
1697
|
);
|
|
1445
|
-
|
|
1698
|
+
writeFileSync9(
|
|
1446
1699
|
sessionMessagesXmlPath(trimmedId, apmRoot),
|
|
1447
1700
|
formatSessionMessagesXml(trimmedId, messages),
|
|
1448
1701
|
"utf8"
|
|
1449
1702
|
);
|
|
1450
1703
|
await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
|
|
1451
1704
|
await syncPlatformRules(cfg, trimmedId, workdir, apmRoot);
|
|
1705
|
+
await syncRemoteDeploymentConfig(workdir, apmRoot);
|
|
1706
|
+
await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
|
|
1452
1707
|
console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
|
|
1453
1708
|
return dir;
|
|
1454
1709
|
}
|
|
@@ -1457,15 +1712,15 @@ async function runPull(sessionId, remoteWorkdir) {
|
|
|
1457
1712
|
import { spawnSync } from "child_process";
|
|
1458
1713
|
|
|
1459
1714
|
// src/version.ts
|
|
1460
|
-
import { readFileSync as
|
|
1461
|
-
import { dirname as
|
|
1715
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
1716
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
1462
1717
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1463
1718
|
var CLI_PACKAGE_NAME = "ai-project-manage-cli";
|
|
1464
1719
|
function readCliVersion() {
|
|
1465
1720
|
try {
|
|
1466
|
-
const dir =
|
|
1467
|
-
const pkgPath =
|
|
1468
|
-
const pkg = JSON.parse(
|
|
1721
|
+
const dir = dirname3(fileURLToPath2(import.meta.url));
|
|
1722
|
+
const pkgPath = join10(dir, "..", "package.json");
|
|
1723
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
1469
1724
|
return pkg.version ?? "0.0.0";
|
|
1470
1725
|
} catch {
|
|
1471
1726
|
return "0.0.0";
|
|
@@ -1538,12 +1793,12 @@ async function runUpdate() {
|
|
|
1538
1793
|
}
|
|
1539
1794
|
|
|
1540
1795
|
// src/commands/update-skills.ts
|
|
1541
|
-
import { existsSync as
|
|
1542
|
-
import { join as
|
|
1796
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
|
|
1797
|
+
import { join as join11 } from "path";
|
|
1543
1798
|
async function syncWorkspaceSkills(cfg, workdir) {
|
|
1544
1799
|
const apmDir = workspaceApmDir(workdir);
|
|
1545
1800
|
const fsApmDir = toFsPath(apmDir);
|
|
1546
|
-
if (!
|
|
1801
|
+
if (!existsSync7(fsApmDir)) {
|
|
1547
1802
|
throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1548
1803
|
}
|
|
1549
1804
|
const apmStat = statSync3(fsApmDir);
|
|
@@ -1555,12 +1810,12 @@ async function syncWorkspaceSkills(cfg, workdir) {
|
|
|
1555
1810
|
if (syncAgentsGuide(apmDir)) {
|
|
1556
1811
|
console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
|
|
1557
1812
|
}
|
|
1558
|
-
const rulesDir =
|
|
1813
|
+
const rulesDir = join11(apmDir, "rules");
|
|
1559
1814
|
const ruleNames = syncBaseRules(rulesDir);
|
|
1560
1815
|
for (const name of ruleNames) {
|
|
1561
1816
|
console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
|
|
1562
1817
|
}
|
|
1563
|
-
const skillsDir =
|
|
1818
|
+
const skillsDir = join11(apmDir, "skills");
|
|
1564
1819
|
mkdirSync4(toFsPath(skillsDir), { recursive: true });
|
|
1565
1820
|
const baseNames = syncBaseSkills(skillsDir);
|
|
1566
1821
|
for (const name of baseNames) {
|
|
@@ -1587,7 +1842,7 @@ async function syncWorkspaceSkills(cfg, workdir) {
|
|
|
1587
1842
|
}
|
|
1588
1843
|
async function runUpdateSkills() {
|
|
1589
1844
|
const apmDir = workspaceApmDir();
|
|
1590
|
-
if (!
|
|
1845
|
+
if (!existsSync7(apmDir)) {
|
|
1591
1846
|
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1592
1847
|
process.exit(1);
|
|
1593
1848
|
}
|
|
@@ -1600,11 +1855,11 @@ async function runUpdateSkills() {
|
|
|
1600
1855
|
}
|
|
1601
1856
|
|
|
1602
1857
|
// src/commands/sync-deploy-config.ts
|
|
1603
|
-
import { existsSync as
|
|
1858
|
+
import { existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
1604
1859
|
async function runSyncDeployConfig() {
|
|
1605
1860
|
const workdir = resolveWorkdirPath();
|
|
1606
1861
|
const apmDir = workspaceApmDir(workdir);
|
|
1607
|
-
if (!
|
|
1862
|
+
if (!existsSync8(apmDir)) {
|
|
1608
1863
|
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
1609
1864
|
process.exit(1);
|
|
1610
1865
|
}
|
|
@@ -1619,18 +1874,33 @@ async function runSyncDeployConfig() {
|
|
|
1619
1874
|
}
|
|
1620
1875
|
}
|
|
1621
1876
|
|
|
1877
|
+
// src/commands/sync-project-documents.ts
|
|
1878
|
+
async function runSyncProjectDocuments(options) {
|
|
1879
|
+
const pull = options?.pull ?? !options?.push;
|
|
1880
|
+
const push = options?.push ?? false;
|
|
1881
|
+
const workdir = resolveWorkdirPath();
|
|
1882
|
+
const apmRoot = workspaceApmDir(workdir);
|
|
1883
|
+
if (pull) {
|
|
1884
|
+
await syncRepositoryProjectDocumentsPull(workdir, apmRoot);
|
|
1885
|
+
}
|
|
1886
|
+
if (push) {
|
|
1887
|
+
const cfg = await ensureLoggedConfig();
|
|
1888
|
+
await syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1622
1892
|
// src/commands/sync-document.ts
|
|
1623
|
-
import { existsSync as
|
|
1893
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1624
1894
|
import { basename as basename3 } from "path";
|
|
1625
1895
|
|
|
1626
1896
|
// src/commands/sync-session-documents.ts
|
|
1627
|
-
import { existsSync as
|
|
1628
|
-
import { join as
|
|
1897
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
1898
|
+
import { join as join12 } from "path";
|
|
1629
1899
|
function listLocalMarkdownFiles(docsDir) {
|
|
1630
|
-
if (!
|
|
1900
|
+
if (!existsSync9(docsDir)) {
|
|
1631
1901
|
return [];
|
|
1632
1902
|
}
|
|
1633
|
-
return
|
|
1903
|
+
return readdirSync4(docsDir).filter(
|
|
1634
1904
|
(name) => name.toLowerCase().endsWith(".md")
|
|
1635
1905
|
);
|
|
1636
1906
|
}
|
|
@@ -1642,8 +1912,8 @@ function remoteDocumentByLocalName(remoteDocuments, localFileName) {
|
|
|
1642
1912
|
});
|
|
1643
1913
|
}
|
|
1644
1914
|
async function upsertLocalDocumentFile(api, sessionId, docsDir, fileName) {
|
|
1645
|
-
const absPath =
|
|
1646
|
-
const content =
|
|
1915
|
+
const absPath = join12(docsDir, fileName);
|
|
1916
|
+
const content = readFileSync8(absPath, "utf8");
|
|
1647
1917
|
const name = documentPlatformName(absPath);
|
|
1648
1918
|
return api.cli.upsertDocument({
|
|
1649
1919
|
sessionId,
|
|
@@ -1665,8 +1935,8 @@ async function syncSessionDocuments(cfg, sessionId, apmRoot, options) {
|
|
|
1665
1935
|
const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ sessionId: trimmedSessionId });
|
|
1666
1936
|
let synced = 0;
|
|
1667
1937
|
for (const fileName of localFiles) {
|
|
1668
|
-
const absPath =
|
|
1669
|
-
const content =
|
|
1938
|
+
const absPath = join12(docsDir, fileName);
|
|
1939
|
+
const content = readFileSync8(absPath, "utf8");
|
|
1670
1940
|
const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
|
|
1671
1941
|
if (remote && remote.content === content) {
|
|
1672
1942
|
continue;
|
|
@@ -1699,7 +1969,7 @@ async function runSyncDocument(sessionId, options) {
|
|
|
1699
1969
|
process.exit(1);
|
|
1700
1970
|
}
|
|
1701
1971
|
const absPath = resolveSessionDocumentPath(trimmedSessionId, fileArg);
|
|
1702
|
-
if (!
|
|
1972
|
+
if (!existsSync10(absPath)) {
|
|
1703
1973
|
const docsDir2 = sessionDocsDir(trimmedSessionId);
|
|
1704
1974
|
console.error(
|
|
1705
1975
|
`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
|
|
@@ -1853,13 +2123,37 @@ function validateCancel(o) {
|
|
|
1853
2123
|
data: { type: "cancel", messageId: o.messageId.trim() }
|
|
1854
2124
|
};
|
|
1855
2125
|
}
|
|
2126
|
+
function validateDeployPush(o) {
|
|
2127
|
+
if (o.type !== "deploy") {
|
|
2128
|
+
return { ok: false, reason: "\u671F\u671B deploy" };
|
|
2129
|
+
}
|
|
2130
|
+
if (!nonEmptyString(o.deploymentRunId)) {
|
|
2131
|
+
return { ok: false, reason: "deploy \u7F3A\u5C11 deploymentRunId" };
|
|
2132
|
+
}
|
|
2133
|
+
if (!nonEmptyString(o.workdir)) {
|
|
2134
|
+
return { ok: false, reason: "deploy \u7F3A\u5C11 workdir" };
|
|
2135
|
+
}
|
|
2136
|
+
const environment = o.environment;
|
|
2137
|
+
if (environment !== "test" && environment !== "online") {
|
|
2138
|
+
return { ok: false, reason: "deploy.environment \u65E0\u6548" };
|
|
2139
|
+
}
|
|
2140
|
+
return {
|
|
2141
|
+
ok: true,
|
|
2142
|
+
data: {
|
|
2143
|
+
type: "deploy",
|
|
2144
|
+
deploymentRunId: o.deploymentRunId.trim(),
|
|
2145
|
+
workdir: o.workdir.trim(),
|
|
2146
|
+
environment
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
1856
2150
|
function validateAgentWsMessage(value, kind) {
|
|
1857
2151
|
if (typeof value !== "object" || value === null) {
|
|
1858
2152
|
return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
|
|
1859
2153
|
}
|
|
1860
2154
|
const o = value;
|
|
1861
2155
|
const type = o.type;
|
|
1862
|
-
if (type !== "heartbeat" && type !== "message" && type !== "cancel") {
|
|
2156
|
+
if (type !== "heartbeat" && type !== "message" && type !== "cancel" && type !== "deploy") {
|
|
1863
2157
|
return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
|
|
1864
2158
|
}
|
|
1865
2159
|
if (kind === "heartbeat" || type === "heartbeat") {
|
|
@@ -1868,9 +2162,125 @@ function validateAgentWsMessage(value, kind) {
|
|
|
1868
2162
|
if (type === "cancel") {
|
|
1869
2163
|
return validateCancel(o);
|
|
1870
2164
|
}
|
|
2165
|
+
if (type === "deploy") {
|
|
2166
|
+
return validateDeployPush(o);
|
|
2167
|
+
}
|
|
1871
2168
|
return validateMessagePush(o);
|
|
1872
2169
|
}
|
|
1873
2170
|
|
|
2171
|
+
// src/commands/connect/deploy-run.ts
|
|
2172
|
+
import { spawn } from "node:child_process";
|
|
2173
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
2174
|
+
import { join as join13 } from "node:path";
|
|
2175
|
+
function readDeployConfig(workdir) {
|
|
2176
|
+
const configPath = join13(workspaceApmDir(workdir), "apm.config.json");
|
|
2177
|
+
try {
|
|
2178
|
+
const raw = readFileSync9(configPath, "utf8");
|
|
2179
|
+
const parsed = JSON.parse(raw);
|
|
2180
|
+
return parsed.deploy;
|
|
2181
|
+
} catch {
|
|
2182
|
+
return void 0;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
function resolveDeployCommand(workdir, environment) {
|
|
2186
|
+
const command = readDeployConfig(workdir)?.[environment];
|
|
2187
|
+
if (typeof command === "string" && command.trim()) {
|
|
2188
|
+
return command.trim();
|
|
2189
|
+
}
|
|
2190
|
+
return null;
|
|
2191
|
+
}
|
|
2192
|
+
function missingDeployCommandMessage(environment) {
|
|
2193
|
+
return `deploy.${environment} \u90E8\u7F72\u547D\u4EE4\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u914D\u7F6E`;
|
|
2194
|
+
}
|
|
2195
|
+
function runShellCommand(command, cwd, signal) {
|
|
2196
|
+
return new Promise((resolve5, reject) => {
|
|
2197
|
+
const child = spawn(command, {
|
|
2198
|
+
cwd,
|
|
2199
|
+
shell: true,
|
|
2200
|
+
env: process.env,
|
|
2201
|
+
windowsHide: true
|
|
2202
|
+
});
|
|
2203
|
+
let stdout = "";
|
|
2204
|
+
let stderr = "";
|
|
2205
|
+
const onAbort = () => {
|
|
2206
|
+
child.kill("SIGTERM");
|
|
2207
|
+
};
|
|
2208
|
+
if (signal.aborted) {
|
|
2209
|
+
onAbort();
|
|
2210
|
+
} else {
|
|
2211
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2212
|
+
}
|
|
2213
|
+
child.stdout.on("data", (chunk) => {
|
|
2214
|
+
stdout += String(chunk);
|
|
2215
|
+
});
|
|
2216
|
+
child.stderr.on("data", (chunk) => {
|
|
2217
|
+
stderr += String(chunk);
|
|
2218
|
+
});
|
|
2219
|
+
child.on("error", (error) => {
|
|
2220
|
+
signal.removeEventListener("abort", onAbort);
|
|
2221
|
+
reject(error);
|
|
2222
|
+
});
|
|
2223
|
+
child.on("close", (code) => {
|
|
2224
|
+
signal.removeEventListener("abort", onAbort);
|
|
2225
|
+
const log = [stdout, stderr].filter(Boolean).join("\n");
|
|
2226
|
+
if (code === 0) {
|
|
2227
|
+
resolve5({ log });
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
const error = new Error(
|
|
2231
|
+
`\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
|
|
2232
|
+
);
|
|
2233
|
+
error.log = log;
|
|
2234
|
+
reject(error);
|
|
2235
|
+
});
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
async function handleInboundDeploy(cfg, msg, signal) {
|
|
2239
|
+
const api = createApmApiClient(cfg);
|
|
2240
|
+
const deploymentRunId = msg.deploymentRunId;
|
|
2241
|
+
if (signal.aborted) return;
|
|
2242
|
+
await api.cli.updateCoordinatorDeploymentStatus({
|
|
2243
|
+
id: deploymentRunId,
|
|
2244
|
+
status: "DEPLOYING"
|
|
2245
|
+
});
|
|
2246
|
+
const workdir = requireRemoteWorkdir(msg.workdir);
|
|
2247
|
+
const command = resolveDeployCommand(workdir, msg.environment);
|
|
2248
|
+
if (!command) {
|
|
2249
|
+
const error = missingDeployCommandMessage(msg.environment);
|
|
2250
|
+
console.error(`[apm] ${error}`);
|
|
2251
|
+
await api.cli.completeCoordinatorDeployment({
|
|
2252
|
+
id: deploymentRunId,
|
|
2253
|
+
status: "FAILED",
|
|
2254
|
+
log: error,
|
|
2255
|
+
error
|
|
2256
|
+
});
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
console.log(
|
|
2260
|
+
`[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir}`
|
|
2261
|
+
);
|
|
2262
|
+
console.log(`[apm] deploy command: ${command}`);
|
|
2263
|
+
try {
|
|
2264
|
+
const { log } = await runShellCommand(command, workdir, signal);
|
|
2265
|
+
await api.cli.completeCoordinatorDeployment({
|
|
2266
|
+
id: deploymentRunId,
|
|
2267
|
+
status: "SUCCESS",
|
|
2268
|
+
log
|
|
2269
|
+
});
|
|
2270
|
+
console.log(`[apm] deploy success id=${deploymentRunId}`);
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2273
|
+
const log = error && typeof error === "object" && "log" in error ? String(error.log ?? "") : "";
|
|
2274
|
+
await api.cli.completeCoordinatorDeployment({
|
|
2275
|
+
id: deploymentRunId,
|
|
2276
|
+
status: "FAILED",
|
|
2277
|
+
log,
|
|
2278
|
+
error: detail
|
|
2279
|
+
});
|
|
2280
|
+
console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
|
|
1874
2284
|
// src/commands/connect/abort-signal-debug.ts
|
|
1875
2285
|
import {
|
|
1876
2286
|
getEventListeners,
|
|
@@ -2100,17 +2510,17 @@ ${JSON.stringify(event, null, 2)}
|
|
|
2100
2510
|
}
|
|
2101
2511
|
|
|
2102
2512
|
// src/commands/connect/agent-session-registry.ts
|
|
2103
|
-
import { existsSync as
|
|
2104
|
-
import { dirname as
|
|
2513
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "node:fs";
|
|
2514
|
+
import { dirname as dirname4, resolve as resolve3 } from "node:path";
|
|
2105
2515
|
function registryPath(workdir, sessionId) {
|
|
2106
2516
|
return resolve3(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
|
|
2107
2517
|
}
|
|
2108
2518
|
function readRegistry(path10) {
|
|
2109
|
-
if (!
|
|
2519
|
+
if (!existsSync11(path10)) {
|
|
2110
2520
|
return {};
|
|
2111
2521
|
}
|
|
2112
2522
|
try {
|
|
2113
|
-
const parsed = JSON.parse(
|
|
2523
|
+
const parsed = JSON.parse(readFileSync10(path10, "utf8"));
|
|
2114
2524
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2115
2525
|
const result = {};
|
|
2116
2526
|
for (const [key, value] of Object.entries(
|
|
@@ -2127,8 +2537,8 @@ function readRegistry(path10) {
|
|
|
2127
2537
|
return {};
|
|
2128
2538
|
}
|
|
2129
2539
|
function writeRegistry(path10, registry) {
|
|
2130
|
-
mkdirSync5(
|
|
2131
|
-
|
|
2540
|
+
mkdirSync5(dirname4(path10), { recursive: true });
|
|
2541
|
+
writeFileSync10(path10, `${JSON.stringify(registry, null, 2)}
|
|
2132
2542
|
`, "utf8");
|
|
2133
2543
|
}
|
|
2134
2544
|
function loadSessionAgentId(workdir, sessionId, user) {
|
|
@@ -2432,20 +2842,20 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
2432
2842
|
}
|
|
2433
2843
|
|
|
2434
2844
|
// src/commands/connect/cli-version-sync.ts
|
|
2435
|
-
import { existsSync as
|
|
2436
|
-
import { join as
|
|
2845
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
2846
|
+
import { join as join14 } from "path";
|
|
2437
2847
|
var CLI_VERSION_FILE = ".cli-version.json";
|
|
2438
2848
|
function manifestPath(apmDir) {
|
|
2439
|
-
return
|
|
2849
|
+
return join14(apmDir, CLI_VERSION_FILE);
|
|
2440
2850
|
}
|
|
2441
2851
|
function loadManifest3(apmDir) {
|
|
2442
2852
|
const path10 = toFsPath(manifestPath(apmDir));
|
|
2443
|
-
if (!
|
|
2853
|
+
if (!existsSync12(path10)) {
|
|
2444
2854
|
return null;
|
|
2445
2855
|
}
|
|
2446
2856
|
try {
|
|
2447
2857
|
const parsed = JSON.parse(
|
|
2448
|
-
|
|
2858
|
+
readFileSync11(path10, "utf8")
|
|
2449
2859
|
);
|
|
2450
2860
|
if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
|
|
2451
2861
|
return parsed;
|
|
@@ -2456,7 +2866,7 @@ function loadManifest3(apmDir) {
|
|
|
2456
2866
|
}
|
|
2457
2867
|
function saveManifest3(apmDir, cliVersion) {
|
|
2458
2868
|
const manifest = { version: 1, cliVersion };
|
|
2459
|
-
|
|
2869
|
+
writeFileSync11(
|
|
2460
2870
|
toFsPath(manifestPath(apmDir)),
|
|
2461
2871
|
`${JSON.stringify(manifest, null, 2)}
|
|
2462
2872
|
`,
|
|
@@ -2568,8 +2978,13 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
2568
2978
|
};
|
|
2569
2979
|
try {
|
|
2570
2980
|
if (signal.aborted) return;
|
|
2571
|
-
|
|
2572
|
-
|
|
2981
|
+
const { didInit } = await runStep(
|
|
2982
|
+
"workspace-init",
|
|
2983
|
+
() => ensureWorkspaceInitialized(workdir)
|
|
2984
|
+
);
|
|
2985
|
+
if (!didInit) {
|
|
2986
|
+
assertApmGitignoredInRepo(workdir);
|
|
2987
|
+
}
|
|
2573
2988
|
await runStep(
|
|
2574
2989
|
"status-typing",
|
|
2575
2990
|
() => updateMessageStatus(cfg, messageId, "TYPING")
|
|
@@ -2633,6 +3048,10 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
2633
3048
|
"sync-documents",
|
|
2634
3049
|
() => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
|
|
2635
3050
|
);
|
|
3051
|
+
await runStep(
|
|
3052
|
+
"sync-project-documents",
|
|
3053
|
+
() => syncRepositoryProjectDocumentsPush(cfg, workdir, apmRoot)
|
|
3054
|
+
);
|
|
2636
3055
|
await runStep(
|
|
2637
3056
|
"commit-files",
|
|
2638
3057
|
() => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
|
|
@@ -2770,6 +3189,27 @@ async function runConnect(options) {
|
|
|
2770
3189
|
activeRuns.get(messageId)?.abort();
|
|
2771
3190
|
return;
|
|
2772
3191
|
}
|
|
3192
|
+
if (validated.data.type === "deploy") {
|
|
3193
|
+
const msg2 = validated.data;
|
|
3194
|
+
const perDeployController = new AbortController();
|
|
3195
|
+
const signal2 = AbortSignal.any([
|
|
3196
|
+
shutdownAbort.signal,
|
|
3197
|
+
perDeployController.signal
|
|
3198
|
+
]);
|
|
3199
|
+
const task2 = (async () => {
|
|
3200
|
+
await runSlots.acquire();
|
|
3201
|
+
try {
|
|
3202
|
+
await handleInboundDeploy(cfg, msg2, signal2);
|
|
3203
|
+
} finally {
|
|
3204
|
+
runSlots.release();
|
|
3205
|
+
}
|
|
3206
|
+
})();
|
|
3207
|
+
activeTasks.add(task2);
|
|
3208
|
+
void task2.finally(() => {
|
|
3209
|
+
activeTasks.delete(task2);
|
|
3210
|
+
});
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
2773
3213
|
if (validated.data.type !== "message") {
|
|
2774
3214
|
return;
|
|
2775
3215
|
}
|
|
@@ -2852,19 +3292,19 @@ async function runCreatePr(options) {
|
|
|
2852
3292
|
import path5 from "node:path";
|
|
2853
3293
|
|
|
2854
3294
|
// src/commands/deploy/internal/apm-config.ts
|
|
2855
|
-
import { existsSync as
|
|
3295
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
|
|
2856
3296
|
import { resolve as resolve4 } from "node:path";
|
|
2857
3297
|
function loadApmConfig(options) {
|
|
2858
3298
|
const p = resolve4(
|
|
2859
3299
|
process.cwd(),
|
|
2860
3300
|
options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
|
|
2861
3301
|
);
|
|
2862
|
-
if (!
|
|
3302
|
+
if (!existsSync13(p)) {
|
|
2863
3303
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
2864
3304
|
process.exit(1);
|
|
2865
3305
|
}
|
|
2866
3306
|
try {
|
|
2867
|
-
const raw =
|
|
3307
|
+
const raw = readFileSync12(p, "utf8");
|
|
2868
3308
|
return JSON.parse(raw);
|
|
2869
3309
|
} catch (e) {
|
|
2870
3310
|
console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
|
|
@@ -2986,7 +3426,7 @@ import path4 from "node:path";
|
|
|
2986
3426
|
import Docker from "dockerode";
|
|
2987
3427
|
|
|
2988
3428
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
|
|
2989
|
-
import { existsSync as
|
|
3429
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
|
|
2990
3430
|
import path from "node:path";
|
|
2991
3431
|
function asOptionalTlsBuffer(value) {
|
|
2992
3432
|
if (typeof value !== "string") {
|
|
@@ -2998,8 +3438,8 @@ function asOptionalTlsBuffer(value) {
|
|
|
2998
3438
|
if (normalized === "") {
|
|
2999
3439
|
return void 0;
|
|
3000
3440
|
}
|
|
3001
|
-
if (
|
|
3002
|
-
return
|
|
3441
|
+
if (existsSync14(normalized)) {
|
|
3442
|
+
return readFileSync13(normalized);
|
|
3003
3443
|
}
|
|
3004
3444
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
3005
3445
|
if (looksLikePath) {
|
|
@@ -3209,7 +3649,7 @@ var DockerodeClient = class {
|
|
|
3209
3649
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
3210
3650
|
|
|
3211
3651
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
|
|
3212
|
-
import { existsSync as
|
|
3652
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
|
|
3213
3653
|
import path2 from "node:path";
|
|
3214
3654
|
function stripSurroundingQuotes(value) {
|
|
3215
3655
|
const t = value.trim();
|
|
@@ -3226,10 +3666,10 @@ function loadEnvFromFile(envFilePath) {
|
|
|
3226
3666
|
return {};
|
|
3227
3667
|
}
|
|
3228
3668
|
const targetPath = path2.resolve(envFilePath);
|
|
3229
|
-
if (!
|
|
3669
|
+
if (!existsSync15(targetPath) || !statSync5(targetPath).isFile()) {
|
|
3230
3670
|
return {};
|
|
3231
3671
|
}
|
|
3232
|
-
const raw =
|
|
3672
|
+
const raw = readFileSync14(targetPath, "utf-8");
|
|
3233
3673
|
const result = {};
|
|
3234
3674
|
for (const line of raw.split(/\r?\n/)) {
|
|
3235
3675
|
const normalized = line.trim();
|
|
@@ -3400,12 +3840,12 @@ function dockerPushImage(params, cwd) {
|
|
|
3400
3840
|
}
|
|
3401
3841
|
|
|
3402
3842
|
// src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
|
|
3403
|
-
import { existsSync as
|
|
3843
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
3404
3844
|
import path3 from "node:path";
|
|
3405
3845
|
function resolveDockerBuildPaths(cwd) {
|
|
3406
3846
|
const dockerfilePath = path3.join(cwd, "Dockerfile");
|
|
3407
3847
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
3408
|
-
if (!
|
|
3848
|
+
if (!existsSync16(dockerfilePath)) {
|
|
3409
3849
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
3410
3850
|
}
|
|
3411
3851
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|
|
@@ -4034,8 +4474,13 @@ function buildProgram() {
|
|
|
4034
4474
|
).action(async () => {
|
|
4035
4475
|
await runSyncDeployConfig();
|
|
4036
4476
|
});
|
|
4477
|
+
program.command("sync-project-documents").description(
|
|
4478
|
+
"\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"
|
|
4479
|
+
).option("--push", "\u63A8\u9001\u672C\u5730 .apm/project/ \u5230\u5E73\u53F0").option("--pull", "\u4ECE\u5E73\u53F0\u62C9\u53D6\u5230 .apm/project/").action(async (opts) => {
|
|
4480
|
+
await runSyncProjectDocuments(opts);
|
|
4481
|
+
});
|
|
4037
4482
|
program.command("pull").description(
|
|
4038
|
-
"\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\
|
|
4483
|
+
"\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
4484
|
).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
|
|
4040
4485
|
await runPull(sessionId);
|
|
4041
4486
|
});
|
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
|
File without changes
|