ai-project-manage-cli 6.0.59 → 6.0.60
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 +280 -273
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -80,8 +80,8 @@ function buildAgentWsUrl(httpBase, apiKey) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// src/commands/init.ts
|
|
83
|
-
import { join as
|
|
84
|
-
import { readFileSync as
|
|
83
|
+
import { join as join5 } from "path";
|
|
84
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
85
85
|
|
|
86
86
|
// src/command-utils.ts
|
|
87
87
|
import {
|
|
@@ -589,6 +589,218 @@ ${diagnostic ?? ""}
|
|
|
589
589
|
return { synced: true, repositoryId, configName: config.name };
|
|
590
590
|
}
|
|
591
591
|
|
|
592
|
+
// src/repository-project-documents-sync.ts
|
|
593
|
+
import {
|
|
594
|
+
existsSync as existsSync2,
|
|
595
|
+
readdirSync as readdirSync2,
|
|
596
|
+
readFileSync as readFileSync3,
|
|
597
|
+
rmSync,
|
|
598
|
+
writeFileSync as writeFileSync4
|
|
599
|
+
} from "fs";
|
|
600
|
+
import { createHash } from "crypto";
|
|
601
|
+
import { dirname as dirname2, join as join4, relative, sep } from "path";
|
|
602
|
+
var MANIFEST_FILE = "manifest.json";
|
|
603
|
+
function projectDocumentsDir(apmRoot) {
|
|
604
|
+
return join4(apmRoot ?? workspaceApmDir(), "project");
|
|
605
|
+
}
|
|
606
|
+
function projectDocumentLocalPath(apmRoot, documentPath) {
|
|
607
|
+
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
608
|
+
return join4(projectDocumentsDir(apmRoot), ...normalized.split("/"));
|
|
609
|
+
}
|
|
610
|
+
function normalizeLocalDocumentPath(path10) {
|
|
611
|
+
const trimmed = path10.trim().replace(/\\/g, "/");
|
|
612
|
+
if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
|
|
613
|
+
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
|
|
614
|
+
}
|
|
615
|
+
const segments = trimmed.split("/").filter(Boolean);
|
|
616
|
+
if (segments.some((segment) => segment === ".." || segment === ".")) {
|
|
617
|
+
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
|
|
618
|
+
}
|
|
619
|
+
return segments.join("/");
|
|
620
|
+
}
|
|
621
|
+
function hashLocalFileContent(content) {
|
|
622
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
623
|
+
}
|
|
624
|
+
function readLocalManifest(apmRoot) {
|
|
625
|
+
const manifestPath2 = join4(projectDocumentsDir(apmRoot), MANIFEST_FILE);
|
|
626
|
+
if (!existsSync2(manifestPath2)) {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
try {
|
|
630
|
+
return JSON.parse(
|
|
631
|
+
readFileSync3(manifestPath2, "utf8")
|
|
632
|
+
);
|
|
633
|
+
} catch {
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function listLocalDocumentPaths(apmRoot) {
|
|
638
|
+
const root = projectDocumentsDir(apmRoot);
|
|
639
|
+
if (!existsSync2(root)) {
|
|
640
|
+
return [];
|
|
641
|
+
}
|
|
642
|
+
const paths = [];
|
|
643
|
+
const walk = (dir) => {
|
|
644
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
645
|
+
const abs = join4(dir, entry.name);
|
|
646
|
+
if (entry.isDirectory()) {
|
|
647
|
+
walk(abs);
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (entry.isFile() && entry.name === MANIFEST_FILE) {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
const rel = relative(root, abs).split(sep).join("/");
|
|
654
|
+
paths.push(rel);
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
walk(root);
|
|
658
|
+
return paths.sort();
|
|
659
|
+
}
|
|
660
|
+
function diffManifestPaths(remote, local) {
|
|
661
|
+
const remoteMap = new Map(
|
|
662
|
+
(remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
663
|
+
);
|
|
664
|
+
const localMap = new Map(
|
|
665
|
+
(local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
666
|
+
);
|
|
667
|
+
const download = [];
|
|
668
|
+
for (const [path10, hash] of remoteMap) {
|
|
669
|
+
if (localMap.get(path10) !== hash) {
|
|
670
|
+
download.push(path10);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const deleteLocal = [];
|
|
674
|
+
for (const path10 of localMap.keys()) {
|
|
675
|
+
if (!remoteMap.has(path10)) {
|
|
676
|
+
deleteLocal.push(path10);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return { download, deleteLocal };
|
|
680
|
+
}
|
|
681
|
+
async function syncRepositoryProjectDocumentsPull(workdirPath, apmDir) {
|
|
682
|
+
const empty = {
|
|
683
|
+
synced: false,
|
|
684
|
+
repositoryId: null,
|
|
685
|
+
downloaded: 0,
|
|
686
|
+
deleted: 0
|
|
687
|
+
};
|
|
688
|
+
const cfg = await tryReadApmConfig();
|
|
689
|
+
if (!cfg || !resolveApiKey(cfg)) {
|
|
690
|
+
console.log(
|
|
691
|
+
"[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"
|
|
692
|
+
);
|
|
693
|
+
return empty;
|
|
694
|
+
}
|
|
695
|
+
const api = createApmApiClient(cfg);
|
|
696
|
+
const { repositoryId, diagnostic } = await resolveRepositoryIdForSync(
|
|
697
|
+
api,
|
|
698
|
+
workdirPath
|
|
699
|
+
);
|
|
700
|
+
if (!repositoryId) {
|
|
701
|
+
console.log(
|
|
702
|
+
`[apm] \u672A\u80FD\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u3002
|
|
703
|
+
${diagnostic ?? ""}`
|
|
704
|
+
);
|
|
705
|
+
return empty;
|
|
706
|
+
}
|
|
707
|
+
const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
|
|
708
|
+
const projectDir = projectDocumentsDir(targetApmDir);
|
|
709
|
+
await ensureDirExists(projectDir);
|
|
710
|
+
const { manifest: remoteManifest } = await api.cli.getRepositoryProjectDocumentManifest({ repositoryId });
|
|
711
|
+
if (!remoteManifest) {
|
|
712
|
+
console.log(
|
|
713
|
+
`[apm] \u4ED3\u5E93 ${repositoryId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`
|
|
714
|
+
);
|
|
715
|
+
return { ...empty, repositoryId };
|
|
716
|
+
}
|
|
717
|
+
const localManifest = readLocalManifest(targetApmDir);
|
|
718
|
+
const { download, deleteLocal } = diffManifestPaths(
|
|
719
|
+
remoteManifest,
|
|
720
|
+
localManifest
|
|
721
|
+
);
|
|
722
|
+
let downloaded = 0;
|
|
723
|
+
if (download.length > 0) {
|
|
724
|
+
const { list } = await api.cli.listRepositoryProjectDocuments({
|
|
725
|
+
repositoryId,
|
|
726
|
+
paths: download.join(",")
|
|
727
|
+
});
|
|
728
|
+
for (const doc of list) {
|
|
729
|
+
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, doc.path));
|
|
730
|
+
await ensureDirExists(dirname2(absPath));
|
|
731
|
+
writeFileSync4(absPath, doc.content, "utf8");
|
|
732
|
+
downloaded += 1;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
let deleted = 0;
|
|
736
|
+
for (const path10 of deleteLocal) {
|
|
737
|
+
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
|
|
738
|
+
if (existsSync2(absPath)) {
|
|
739
|
+
rmSync(absPath, { force: true });
|
|
740
|
+
deleted += 1;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
writeFileSync4(
|
|
744
|
+
toFsPath(join4(projectDir, MANIFEST_FILE)),
|
|
745
|
+
`${JSON.stringify(remoteManifest, null, 2)}
|
|
746
|
+
`,
|
|
747
|
+
"utf8"
|
|
748
|
+
);
|
|
749
|
+
console.log(
|
|
750
|
+
`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted}`
|
|
751
|
+
);
|
|
752
|
+
return {
|
|
753
|
+
synced: true,
|
|
754
|
+
repositoryId,
|
|
755
|
+
downloaded,
|
|
756
|
+
deleted
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
|
|
760
|
+
const api = createApmApiClient(cfg);
|
|
761
|
+
const { repositoryId } = await resolveRepositoryIdForSync(api, workdirPath);
|
|
762
|
+
if (!repositoryId) {
|
|
763
|
+
return 0;
|
|
764
|
+
}
|
|
765
|
+
const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
|
|
766
|
+
const localPaths = listLocalDocumentPaths(targetApmDir);
|
|
767
|
+
if (localPaths.length === 0) {
|
|
768
|
+
console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
769
|
+
return 0;
|
|
770
|
+
}
|
|
771
|
+
const remoteManifest = (await api.cli.getRepositoryProjectDocumentManifest({ repositoryId })).manifest ?? null;
|
|
772
|
+
const remoteHashByPath = new Map(
|
|
773
|
+
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
774
|
+
);
|
|
775
|
+
const remoteDescriptionByPath = new Map(
|
|
776
|
+
(remoteManifest?.documents ?? []).map((doc) => [
|
|
777
|
+
doc.path,
|
|
778
|
+
doc.description
|
|
779
|
+
])
|
|
780
|
+
);
|
|
781
|
+
let synced = 0;
|
|
782
|
+
for (const path10 of localPaths) {
|
|
783
|
+
const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
|
|
784
|
+
const content = readFileSync3(absPath, "utf8");
|
|
785
|
+
const contentHash = hashLocalFileContent(content);
|
|
786
|
+
if (remoteHashByPath.get(path10) === contentHash) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
await api.cli.upsertRepositoryProjectDocument({
|
|
790
|
+
repositoryId,
|
|
791
|
+
path: path10,
|
|
792
|
+
content,
|
|
793
|
+
description: remoteDescriptionByPath.get(path10) ?? void 0
|
|
794
|
+
});
|
|
795
|
+
synced += 1;
|
|
796
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: ${path10}`);
|
|
797
|
+
}
|
|
798
|
+
if (synced === 0) {
|
|
799
|
+
console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
800
|
+
}
|
|
801
|
+
return synced;
|
|
802
|
+
}
|
|
803
|
+
|
|
592
804
|
// src/git-utils.ts
|
|
593
805
|
import { execFile as execFile2 } from "child_process";
|
|
594
806
|
import { promisify as promisify2 } from "util";
|
|
@@ -666,13 +878,14 @@ async function ensureWorkspaceInitialized(workdir, options) {
|
|
|
666
878
|
const apmDir = workspaceApmDir(workdir);
|
|
667
879
|
await copyTemplateFiles(apmDir, workdir);
|
|
668
880
|
const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
881
|
+
await syncRepositoryProjectDocumentsPull(workdir, apmDir);
|
|
669
882
|
const trimmedName = options?.name?.trim();
|
|
670
883
|
if (trimmedName) {
|
|
671
|
-
const apmConfigPath = toFsPath(
|
|
672
|
-
const config =
|
|
884
|
+
const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
|
|
885
|
+
const config = readFileSync4(apmConfigPath, "utf8");
|
|
673
886
|
const configJson = JSON.parse(config);
|
|
674
887
|
configJson.name = trimmedName;
|
|
675
|
-
|
|
888
|
+
writeFileSync5(
|
|
676
889
|
apmConfigPath,
|
|
677
890
|
`${JSON.stringify(configJson, null, 2)}
|
|
678
891
|
`,
|
|
@@ -703,7 +916,7 @@ async function runInit(name) {
|
|
|
703
916
|
}
|
|
704
917
|
|
|
705
918
|
// src/commands/login.ts
|
|
706
|
-
import { existsSync as
|
|
919
|
+
import { existsSync as existsSync3 } from "fs";
|
|
707
920
|
import { ApiError } from "listpage-http";
|
|
708
921
|
async function runLogin(opts) {
|
|
709
922
|
const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
@@ -758,7 +971,7 @@ async function runLogin(opts) {
|
|
|
758
971
|
);
|
|
759
972
|
const workdir = resolveWorkdirPath();
|
|
760
973
|
const apmDir = workspaceApmDir(workdir);
|
|
761
|
-
if (
|
|
974
|
+
if (existsSync3(apmDir)) {
|
|
762
975
|
await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
763
976
|
}
|
|
764
977
|
}
|
|
@@ -1161,9 +1374,9 @@ function formatSessionMessagesXml(sessionId, messages) {
|
|
|
1161
1374
|
}
|
|
1162
1375
|
|
|
1163
1376
|
// src/commands/sync-session-attachments.ts
|
|
1164
|
-
import { existsSync as
|
|
1165
|
-
import { join as
|
|
1166
|
-
var
|
|
1377
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
1378
|
+
import { join as join6 } from "path";
|
|
1379
|
+
var MANIFEST_FILE2 = ".sync-manifest.json";
|
|
1167
1380
|
async function downloadAttachment(cfg, attachmentId) {
|
|
1168
1381
|
const base = cfg.baseUrl.trim().replace(/\/+$/, "");
|
|
1169
1382
|
const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({ attachmentId })}`;
|
|
@@ -1176,13 +1389,13 @@ async function downloadAttachment(cfg, attachmentId) {
|
|
|
1176
1389
|
return Buffer.from(await res.arrayBuffer());
|
|
1177
1390
|
}
|
|
1178
1391
|
function loadManifest(dir) {
|
|
1179
|
-
const path10 =
|
|
1180
|
-
if (!
|
|
1392
|
+
const path10 = join6(dir, MANIFEST_FILE2);
|
|
1393
|
+
if (!existsSync4(path10)) {
|
|
1181
1394
|
return { version: 1, attachments: {} };
|
|
1182
1395
|
}
|
|
1183
1396
|
try {
|
|
1184
1397
|
const parsed = JSON.parse(
|
|
1185
|
-
|
|
1398
|
+
readFileSync5(path10, "utf8")
|
|
1186
1399
|
);
|
|
1187
1400
|
if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
|
|
1188
1401
|
return parsed;
|
|
@@ -1192,21 +1405,21 @@ function loadManifest(dir) {
|
|
|
1192
1405
|
return { version: 1, attachments: {} };
|
|
1193
1406
|
}
|
|
1194
1407
|
function saveManifest(dir, manifest) {
|
|
1195
|
-
|
|
1196
|
-
|
|
1408
|
+
writeFileSync6(
|
|
1409
|
+
join6(dir, MANIFEST_FILE2),
|
|
1197
1410
|
`${JSON.stringify(manifest, null, 2)}
|
|
1198
1411
|
`,
|
|
1199
1412
|
"utf8"
|
|
1200
1413
|
);
|
|
1201
1414
|
}
|
|
1202
1415
|
function isAttachmentUpToDate(entry, item, dest) {
|
|
1203
|
-
if (!entry || !
|
|
1416
|
+
if (!entry || !existsSync4(dest)) return false;
|
|
1204
1417
|
if (entry.name !== item.name) return false;
|
|
1205
1418
|
const createdAt = item.createdAt ?? "";
|
|
1206
1419
|
return entry.createdAt === createdAt;
|
|
1207
1420
|
}
|
|
1208
1421
|
async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
1209
|
-
const dir =
|
|
1422
|
+
const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
|
|
1210
1423
|
await ensureDirExists(dir);
|
|
1211
1424
|
if (attachments.length === 0) {
|
|
1212
1425
|
saveManifest(dir, { version: 1, attachments: {} });
|
|
@@ -1215,7 +1428,7 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
|
1215
1428
|
const manifest = loadManifest(dir);
|
|
1216
1429
|
const nextManifest = { version: 1, attachments: {} };
|
|
1217
1430
|
for (const item of attachments) {
|
|
1218
|
-
const dest =
|
|
1431
|
+
const dest = join6(dir, item.name);
|
|
1219
1432
|
const entry = manifest.attachments[item.id];
|
|
1220
1433
|
const createdAt = item.createdAt ?? "";
|
|
1221
1434
|
if (isAttachmentUpToDate(entry, item, dest)) {
|
|
@@ -1226,7 +1439,7 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
|
1226
1439
|
continue;
|
|
1227
1440
|
}
|
|
1228
1441
|
const buffer = await downloadAttachment(cfg, item.id);
|
|
1229
|
-
|
|
1442
|
+
writeFileSync6(dest, buffer);
|
|
1230
1443
|
nextManifest.attachments[item.id] = {
|
|
1231
1444
|
name: item.name,
|
|
1232
1445
|
createdAt
|
|
@@ -1237,46 +1450,46 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
|
1237
1450
|
}
|
|
1238
1451
|
|
|
1239
1452
|
// src/rules-sync.ts
|
|
1240
|
-
import { basename as basename2, extname as extname2, join as
|
|
1241
|
-
import { existsSync as
|
|
1453
|
+
import { basename as basename2, extname as extname2, join as join8 } from "path";
|
|
1454
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
1242
1455
|
|
|
1243
1456
|
// src/skills-sync.ts
|
|
1244
1457
|
import {
|
|
1245
1458
|
copyFileSync as copyFileSync2,
|
|
1246
1459
|
cpSync,
|
|
1247
|
-
existsSync as
|
|
1460
|
+
existsSync as existsSync5,
|
|
1248
1461
|
mkdirSync as mkdirSync3,
|
|
1249
|
-
readdirSync as
|
|
1250
|
-
rmSync,
|
|
1462
|
+
readdirSync as readdirSync3,
|
|
1463
|
+
rmSync as rmSync2,
|
|
1251
1464
|
statSync as statSync2,
|
|
1252
|
-
writeFileSync as
|
|
1465
|
+
writeFileSync as writeFileSync7
|
|
1253
1466
|
} from "fs";
|
|
1254
|
-
import { join as
|
|
1255
|
-
var AGENTS_TEMPLATE_PATH =
|
|
1256
|
-
var BASE_SKILLS_TEMPLATE_DIR =
|
|
1257
|
-
var BASE_RULES_TEMPLATE_DIR =
|
|
1467
|
+
import { join as join7 } from "path";
|
|
1468
|
+
var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
|
|
1469
|
+
var BASE_SKILLS_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "skills");
|
|
1470
|
+
var BASE_RULES_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "rules");
|
|
1258
1471
|
function sanitizeSkillDirName(name) {
|
|
1259
1472
|
const trimmed = name.trim();
|
|
1260
1473
|
if (!trimmed) return "skill";
|
|
1261
1474
|
return trimmed.replace(/[/\\:*?"<>|]/g, "_");
|
|
1262
1475
|
}
|
|
1263
1476
|
function listBaseSkillDirNames() {
|
|
1264
|
-
if (!
|
|
1265
|
-
return
|
|
1266
|
-
const path10 =
|
|
1477
|
+
if (!existsSync5(BASE_SKILLS_TEMPLATE_DIR)) return [];
|
|
1478
|
+
return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
|
|
1479
|
+
const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
|
|
1267
1480
|
return statSync2(path10).isDirectory();
|
|
1268
1481
|
});
|
|
1269
1482
|
}
|
|
1270
1483
|
function syncAgentsGuide(apmDir) {
|
|
1271
|
-
if (!
|
|
1484
|
+
if (!existsSync5(AGENTS_TEMPLATE_PATH)) return false;
|
|
1272
1485
|
mkdirSync3(apmDir, { recursive: true });
|
|
1273
|
-
copyFileSync2(AGENTS_TEMPLATE_PATH,
|
|
1486
|
+
copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
|
|
1274
1487
|
return true;
|
|
1275
1488
|
}
|
|
1276
1489
|
function listBaseRuleFileNames() {
|
|
1277
|
-
if (!
|
|
1278
|
-
return
|
|
1279
|
-
const path10 =
|
|
1490
|
+
if (!existsSync5(BASE_RULES_TEMPLATE_DIR)) return [];
|
|
1491
|
+
return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
|
|
1492
|
+
const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
|
|
1280
1493
|
return statSync2(path10).isFile();
|
|
1281
1494
|
});
|
|
1282
1495
|
}
|
|
@@ -1284,8 +1497,8 @@ function syncBaseRules(rulesDir) {
|
|
|
1284
1497
|
mkdirSync3(rulesDir, { recursive: true });
|
|
1285
1498
|
const names = listBaseRuleFileNames();
|
|
1286
1499
|
for (const name of names) {
|
|
1287
|
-
const src =
|
|
1288
|
-
const dest =
|
|
1500
|
+
const src = join7(BASE_RULES_TEMPLATE_DIR, name);
|
|
1501
|
+
const dest = join7(rulesDir, name);
|
|
1289
1502
|
copyFileSync2(src, dest);
|
|
1290
1503
|
}
|
|
1291
1504
|
return names;
|
|
@@ -1294,8 +1507,8 @@ function syncBaseSkills(skillsDir) {
|
|
|
1294
1507
|
mkdirSync3(skillsDir, { recursive: true });
|
|
1295
1508
|
const names = listBaseSkillDirNames();
|
|
1296
1509
|
for (const name of names) {
|
|
1297
|
-
const src =
|
|
1298
|
-
const dest =
|
|
1510
|
+
const src = join7(BASE_SKILLS_TEMPLATE_DIR, name);
|
|
1511
|
+
const dest = join7(skillsDir, name);
|
|
1299
1512
|
cpSync(src, dest, { recursive: true, force: true });
|
|
1300
1513
|
}
|
|
1301
1514
|
return names;
|
|
@@ -1312,26 +1525,26 @@ function syncSupplementarySkills(skillsDir, list) {
|
|
|
1312
1525
|
skipped.push(dirName);
|
|
1313
1526
|
continue;
|
|
1314
1527
|
}
|
|
1315
|
-
const skillDir =
|
|
1528
|
+
const skillDir = join7(skillsDir, dirName);
|
|
1316
1529
|
mkdirSync3(skillDir, { recursive: true });
|
|
1317
|
-
|
|
1530
|
+
writeFileSync7(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
|
|
1318
1531
|
written.push(dirName);
|
|
1319
1532
|
}
|
|
1320
1533
|
const removed = [];
|
|
1321
|
-
if (!
|
|
1322
|
-
for (const entry of
|
|
1323
|
-
const full =
|
|
1534
|
+
if (!existsSync5(skillsDir)) return { written, skipped, removed };
|
|
1535
|
+
for (const entry of readdirSync3(skillsDir)) {
|
|
1536
|
+
const full = join7(skillsDir, entry);
|
|
1324
1537
|
if (!statSync2(full).isDirectory()) continue;
|
|
1325
1538
|
if (baseNames.has(entry)) continue;
|
|
1326
1539
|
if (apiDirNames.has(entry)) continue;
|
|
1327
|
-
|
|
1540
|
+
rmSync2(full, { recursive: true, force: true });
|
|
1328
1541
|
removed.push(entry);
|
|
1329
1542
|
}
|
|
1330
1543
|
return { written, skipped, removed };
|
|
1331
1544
|
}
|
|
1332
1545
|
|
|
1333
1546
|
// src/rules-sync.ts
|
|
1334
|
-
var
|
|
1547
|
+
var MANIFEST_FILE3 = ".rules-sync-manifest.json";
|
|
1335
1548
|
function ruleLocalFileName(ruleName) {
|
|
1336
1549
|
const trimmed = ruleName.trim();
|
|
1337
1550
|
if (!trimmed) return "rule.md";
|
|
@@ -1340,13 +1553,13 @@ function ruleLocalFileName(ruleName) {
|
|
|
1340
1553
|
return `${sanitized}.md`;
|
|
1341
1554
|
}
|
|
1342
1555
|
function loadManifest2(rulesDir) {
|
|
1343
|
-
const path10 =
|
|
1344
|
-
if (!
|
|
1556
|
+
const path10 = join8(rulesDir, MANIFEST_FILE3);
|
|
1557
|
+
if (!existsSync6(toFsPath(path10))) {
|
|
1345
1558
|
return { version: 1, rules: {} };
|
|
1346
1559
|
}
|
|
1347
1560
|
try {
|
|
1348
1561
|
const parsed = JSON.parse(
|
|
1349
|
-
|
|
1562
|
+
readFileSync6(toFsPath(path10), "utf8")
|
|
1350
1563
|
);
|
|
1351
1564
|
if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
|
|
1352
1565
|
return parsed;
|
|
@@ -1356,8 +1569,8 @@ function loadManifest2(rulesDir) {
|
|
|
1356
1569
|
return { version: 1, rules: {} };
|
|
1357
1570
|
}
|
|
1358
1571
|
function saveManifest2(rulesDir, manifest) {
|
|
1359
|
-
|
|
1360
|
-
toFsPath(
|
|
1572
|
+
writeFileSync8(
|
|
1573
|
+
toFsPath(join8(rulesDir, MANIFEST_FILE3)),
|
|
1361
1574
|
`${JSON.stringify(manifest, null, 2)}
|
|
1362
1575
|
`,
|
|
1363
1576
|
"utf8"
|
|
@@ -1367,18 +1580,18 @@ function isBaseRuleFileName(fileName) {
|
|
|
1367
1580
|
return listBaseRuleFileNames().includes(basename2(fileName));
|
|
1368
1581
|
}
|
|
1369
1582
|
function isRuleUpToDate(entry, rule, dest) {
|
|
1370
|
-
if (!entry || !
|
|
1583
|
+
if (!entry || !existsSync6(toFsPath(dest))) return false;
|
|
1371
1584
|
if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
|
|
1372
1585
|
const updatedAt = rule.updatedAt ?? "";
|
|
1373
1586
|
if (entry.updatedAt !== updatedAt) return false;
|
|
1374
|
-
const localContent =
|
|
1587
|
+
const localContent = readFileSync6(toFsPath(dest), "utf8");
|
|
1375
1588
|
return localContent === (rule.content ?? "");
|
|
1376
1589
|
}
|
|
1377
1590
|
async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
1378
1591
|
const api = createApmApiClient(cfg);
|
|
1379
1592
|
const baseline = await api.cli.branchBaseline({ sessionId, workdirPath });
|
|
1380
1593
|
const repositoryId = baseline.repositoryId;
|
|
1381
|
-
const rulesDir =
|
|
1594
|
+
const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
|
|
1382
1595
|
await ensureDirExists(rulesDir);
|
|
1383
1596
|
if (!repositoryId) {
|
|
1384
1597
|
console.log(
|
|
@@ -1395,7 +1608,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
1395
1608
|
for (const rule of list) {
|
|
1396
1609
|
remoteIds.add(rule.id);
|
|
1397
1610
|
const fileName = ruleLocalFileName(rule.name);
|
|
1398
|
-
const dest =
|
|
1611
|
+
const dest = join8(rulesDir, fileName);
|
|
1399
1612
|
const entry = manifest.rules[rule.id];
|
|
1400
1613
|
const updatedAt = rule.updatedAt ?? "";
|
|
1401
1614
|
if (isRuleUpToDate(entry, rule, dest)) {
|
|
@@ -1404,7 +1617,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
1404
1617
|
console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
|
|
1405
1618
|
continue;
|
|
1406
1619
|
}
|
|
1407
|
-
|
|
1620
|
+
writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
|
|
1408
1621
|
nextManifest.rules[rule.id] = { fileName, updatedAt };
|
|
1409
1622
|
written.push(fileName);
|
|
1410
1623
|
console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
|
|
@@ -1413,9 +1626,9 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
1413
1626
|
for (const [ruleId, entry] of Object.entries(manifest.rules)) {
|
|
1414
1627
|
if (remoteIds.has(ruleId)) continue;
|
|
1415
1628
|
if (isBaseRuleFileName(entry.fileName)) continue;
|
|
1416
|
-
const dest =
|
|
1417
|
-
if (
|
|
1418
|
-
|
|
1629
|
+
const dest = join8(rulesDir, entry.fileName);
|
|
1630
|
+
if (existsSync6(toFsPath(dest))) {
|
|
1631
|
+
rmSync3(toFsPath(dest), { force: true });
|
|
1419
1632
|
}
|
|
1420
1633
|
removed.push(entry.fileName);
|
|
1421
1634
|
console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
|
|
@@ -1424,218 +1637,6 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
1424
1637
|
return { written, skipped, removed, repositoryId };
|
|
1425
1638
|
}
|
|
1426
1639
|
|
|
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
|
-
|
|
1639
1640
|
// src/commands/pull.ts
|
|
1640
1641
|
async function runPull(sessionId, remoteWorkdir) {
|
|
1641
1642
|
const trimmedId = sessionId.trim();
|
|
@@ -3028,6 +3029,12 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
3028
3029
|
console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
|
|
3029
3030
|
}
|
|
3030
3031
|
if (signal.aborted) return;
|
|
3032
|
+
if (!pullRan) {
|
|
3033
|
+
await runStep(
|
|
3034
|
+
"sync-project-documents-pull",
|
|
3035
|
+
() => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
|
|
3036
|
+
);
|
|
3037
|
+
}
|
|
3031
3038
|
await runStep(
|
|
3032
3039
|
"cursor-agent",
|
|
3033
3040
|
() => runCursorAgent(
|