ai-project-manage-cli 6.0.58 → 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 CHANGED
@@ -80,8 +80,8 @@ function buildAgentWsUrl(httpBase, apiKey) {
80
80
  }
81
81
 
82
82
  // src/commands/init.ts
83
- import { join as join4 } from "path";
84
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
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 {
@@ -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 assertWorkspaceApmDirExists(workdir) {
152
+ function isWorkspaceApmInitialized(workdir) {
153
153
  const apmDir = workspaceApmDir(workdir);
154
154
  const fsApmDir = toFsPath(apmDir);
155
155
  if (!existsSync(fsApmDir)) {
156
- throw new Error(
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\/?$/,
@@ -590,6 +589,218 @@ ${diagnostic ?? ""}
590
589
  return { synced: true, repositoryId, configName: config.name };
591
590
  }
592
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
+
593
804
  // src/git-utils.ts
594
805
  import { execFile as execFile2 } from "child_process";
595
806
  import { promisify as promisify2 } from "util";
@@ -652,8 +863,13 @@ async function commitAndPushGitignore(workdir) {
652
863
  }
653
864
 
654
865
  // src/commands/init.ts
655
- async function runInit(name) {
656
- const workdir = resolveWorkdirPath();
866
+ async function ensureWorkspaceInitialized(workdir, options) {
867
+ if (isWorkspaceApmInitialized(workdir)) {
868
+ return { didInit: false };
869
+ }
870
+ console.log(
871
+ `[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`
872
+ );
657
873
  await ensureWorkspaceApmDirForInit(workdir);
658
874
  if (ensureApmGitignoredInRepo(workdir)) {
659
875
  console.log("[apm] \u5DF2\u5728 .gitignore \u4E2D\u6DFB\u52A0 **/.apm/**");
@@ -662,13 +878,14 @@ async function runInit(name) {
662
878
  const apmDir = workspaceApmDir(workdir);
663
879
  await copyTemplateFiles(apmDir, workdir);
664
880
  const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
665
- const trimmedName = name?.trim();
881
+ await syncRepositoryProjectDocumentsPull(workdir, apmDir);
882
+ const trimmedName = options?.name?.trim();
666
883
  if (trimmedName) {
667
- const apmConfigPath = toFsPath(join4(apmDir, "apm.config.json"));
668
- const config = readFileSync3(apmConfigPath, "utf8");
884
+ const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
885
+ const config = readFileSync4(apmConfigPath, "utf8");
669
886
  const configJson = JSON.parse(config);
670
887
  configJson.name = trimmedName;
671
- writeFileSync4(
888
+ writeFileSync5(
672
889
  apmConfigPath,
673
890
  `${JSON.stringify(configJson, null, 2)}
674
891
  `,
@@ -676,8 +893,21 @@ async function runInit(name) {
676
893
  );
677
894
  }
678
895
  console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
896
+ return { didInit: true, syncResult };
897
+ }
898
+ async function runInit(name) {
899
+ const workdir = resolveWorkdirPath();
900
+ await ensureWorkspaceApmDirForInit(workdir);
901
+ const { didInit, syncResult } = await ensureWorkspaceInitialized(workdir, {
902
+ name
903
+ });
904
+ if (!didInit) {
905
+ throw new Error(
906
+ "[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"
907
+ );
908
+ }
679
909
  console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
680
- if (!syncResult.synced) {
910
+ if (syncResult && !syncResult.synced) {
681
911
  console.log(
682
912
  "[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"
683
913
  );
@@ -686,7 +916,7 @@ async function runInit(name) {
686
916
  }
687
917
 
688
918
  // src/commands/login.ts
689
- import { existsSync as existsSync2 } from "fs";
919
+ import { existsSync as existsSync3 } from "fs";
690
920
  import { ApiError } from "listpage-http";
691
921
  async function runLogin(opts) {
692
922
  const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
@@ -741,7 +971,7 @@ async function runLogin(opts) {
741
971
  );
742
972
  const workdir = resolveWorkdirPath();
743
973
  const apmDir = workspaceApmDir(workdir);
744
- if (existsSync2(apmDir)) {
974
+ if (existsSync3(apmDir)) {
745
975
  await syncRemoteDeploymentConfig(workdir, apmDir);
746
976
  }
747
977
  }
@@ -1144,9 +1374,9 @@ function formatSessionMessagesXml(sessionId, messages) {
1144
1374
  }
1145
1375
 
1146
1376
  // src/commands/sync-session-attachments.ts
1147
- import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
1148
- import { join as join5 } from "path";
1149
- var MANIFEST_FILE = ".sync-manifest.json";
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";
1150
1380
  async function downloadAttachment(cfg, attachmentId) {
1151
1381
  const base = cfg.baseUrl.trim().replace(/\/+$/, "");
1152
1382
  const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({ attachmentId })}`;
@@ -1159,13 +1389,13 @@ async function downloadAttachment(cfg, attachmentId) {
1159
1389
  return Buffer.from(await res.arrayBuffer());
1160
1390
  }
1161
1391
  function loadManifest(dir) {
1162
- const path10 = join5(dir, MANIFEST_FILE);
1163
- if (!existsSync3(path10)) {
1392
+ const path10 = join6(dir, MANIFEST_FILE2);
1393
+ if (!existsSync4(path10)) {
1164
1394
  return { version: 1, attachments: {} };
1165
1395
  }
1166
1396
  try {
1167
1397
  const parsed = JSON.parse(
1168
- readFileSync4(path10, "utf8")
1398
+ readFileSync5(path10, "utf8")
1169
1399
  );
1170
1400
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
1171
1401
  return parsed;
@@ -1175,21 +1405,21 @@ function loadManifest(dir) {
1175
1405
  return { version: 1, attachments: {} };
1176
1406
  }
1177
1407
  function saveManifest(dir, manifest) {
1178
- writeFileSync5(
1179
- join5(dir, MANIFEST_FILE),
1408
+ writeFileSync6(
1409
+ join6(dir, MANIFEST_FILE2),
1180
1410
  `${JSON.stringify(manifest, null, 2)}
1181
1411
  `,
1182
1412
  "utf8"
1183
1413
  );
1184
1414
  }
1185
1415
  function isAttachmentUpToDate(entry, item, dest) {
1186
- if (!entry || !existsSync3(dest)) return false;
1416
+ if (!entry || !existsSync4(dest)) return false;
1187
1417
  if (entry.name !== item.name) return false;
1188
1418
  const createdAt = item.createdAt ?? "";
1189
1419
  return entry.createdAt === createdAt;
1190
1420
  }
1191
1421
  async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
1192
- const dir = join5(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
1422
+ const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
1193
1423
  await ensureDirExists(dir);
1194
1424
  if (attachments.length === 0) {
1195
1425
  saveManifest(dir, { version: 1, attachments: {} });
@@ -1198,7 +1428,7 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
1198
1428
  const manifest = loadManifest(dir);
1199
1429
  const nextManifest = { version: 1, attachments: {} };
1200
1430
  for (const item of attachments) {
1201
- const dest = join5(dir, item.name);
1431
+ const dest = join6(dir, item.name);
1202
1432
  const entry = manifest.attachments[item.id];
1203
1433
  const createdAt = item.createdAt ?? "";
1204
1434
  if (isAttachmentUpToDate(entry, item, dest)) {
@@ -1209,7 +1439,7 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
1209
1439
  continue;
1210
1440
  }
1211
1441
  const buffer = await downloadAttachment(cfg, item.id);
1212
- writeFileSync5(dest, buffer);
1442
+ writeFileSync6(dest, buffer);
1213
1443
  nextManifest.attachments[item.id] = {
1214
1444
  name: item.name,
1215
1445
  createdAt
@@ -1220,46 +1450,46 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
1220
1450
  }
1221
1451
 
1222
1452
  // src/rules-sync.ts
1223
- import { basename as basename2, extname as extname2, join as join7 } from "path";
1224
- import { existsSync as existsSync5, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
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";
1225
1455
 
1226
1456
  // src/skills-sync.ts
1227
1457
  import {
1228
1458
  copyFileSync as copyFileSync2,
1229
1459
  cpSync,
1230
- existsSync as existsSync4,
1460
+ existsSync as existsSync5,
1231
1461
  mkdirSync as mkdirSync3,
1232
- readdirSync as readdirSync2,
1233
- rmSync,
1462
+ readdirSync as readdirSync3,
1463
+ rmSync as rmSync2,
1234
1464
  statSync as statSync2,
1235
- writeFileSync as writeFileSync6
1465
+ writeFileSync as writeFileSync7
1236
1466
  } from "fs";
1237
- import { join as join6 } from "path";
1238
- var AGENTS_TEMPLATE_PATH = join6(CLI_TEMPLATE_DIR, "AGENTS.md");
1239
- var BASE_SKILLS_TEMPLATE_DIR = join6(CLI_TEMPLATE_DIR, "skills");
1240
- var BASE_RULES_TEMPLATE_DIR = join6(CLI_TEMPLATE_DIR, "rules");
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");
1241
1471
  function sanitizeSkillDirName(name) {
1242
1472
  const trimmed = name.trim();
1243
1473
  if (!trimmed) return "skill";
1244
1474
  return trimmed.replace(/[/\\:*?"<>|]/g, "_");
1245
1475
  }
1246
1476
  function listBaseSkillDirNames() {
1247
- if (!existsSync4(BASE_SKILLS_TEMPLATE_DIR)) return [];
1248
- return readdirSync2(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
1249
- const path10 = join6(BASE_SKILLS_TEMPLATE_DIR, name);
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);
1250
1480
  return statSync2(path10).isDirectory();
1251
1481
  });
1252
1482
  }
1253
1483
  function syncAgentsGuide(apmDir) {
1254
- if (!existsSync4(AGENTS_TEMPLATE_PATH)) return false;
1484
+ if (!existsSync5(AGENTS_TEMPLATE_PATH)) return false;
1255
1485
  mkdirSync3(apmDir, { recursive: true });
1256
- copyFileSync2(AGENTS_TEMPLATE_PATH, join6(apmDir, "AGENTS.md"));
1486
+ copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
1257
1487
  return true;
1258
1488
  }
1259
1489
  function listBaseRuleFileNames() {
1260
- if (!existsSync4(BASE_RULES_TEMPLATE_DIR)) return [];
1261
- return readdirSync2(BASE_RULES_TEMPLATE_DIR).filter((name) => {
1262
- const path10 = join6(BASE_RULES_TEMPLATE_DIR, name);
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);
1263
1493
  return statSync2(path10).isFile();
1264
1494
  });
1265
1495
  }
@@ -1267,8 +1497,8 @@ function syncBaseRules(rulesDir) {
1267
1497
  mkdirSync3(rulesDir, { recursive: true });
1268
1498
  const names = listBaseRuleFileNames();
1269
1499
  for (const name of names) {
1270
- const src = join6(BASE_RULES_TEMPLATE_DIR, name);
1271
- const dest = join6(rulesDir, name);
1500
+ const src = join7(BASE_RULES_TEMPLATE_DIR, name);
1501
+ const dest = join7(rulesDir, name);
1272
1502
  copyFileSync2(src, dest);
1273
1503
  }
1274
1504
  return names;
@@ -1277,8 +1507,8 @@ function syncBaseSkills(skillsDir) {
1277
1507
  mkdirSync3(skillsDir, { recursive: true });
1278
1508
  const names = listBaseSkillDirNames();
1279
1509
  for (const name of names) {
1280
- const src = join6(BASE_SKILLS_TEMPLATE_DIR, name);
1281
- const dest = join6(skillsDir, name);
1510
+ const src = join7(BASE_SKILLS_TEMPLATE_DIR, name);
1511
+ const dest = join7(skillsDir, name);
1282
1512
  cpSync(src, dest, { recursive: true, force: true });
1283
1513
  }
1284
1514
  return names;
@@ -1295,26 +1525,26 @@ function syncSupplementarySkills(skillsDir, list) {
1295
1525
  skipped.push(dirName);
1296
1526
  continue;
1297
1527
  }
1298
- const skillDir = join6(skillsDir, dirName);
1528
+ const skillDir = join7(skillsDir, dirName);
1299
1529
  mkdirSync3(skillDir, { recursive: true });
1300
- writeFileSync6(join6(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
1530
+ writeFileSync7(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
1301
1531
  written.push(dirName);
1302
1532
  }
1303
1533
  const removed = [];
1304
- if (!existsSync4(skillsDir)) return { written, skipped, removed };
1305
- for (const entry of readdirSync2(skillsDir)) {
1306
- const full = join6(skillsDir, entry);
1534
+ if (!existsSync5(skillsDir)) return { written, skipped, removed };
1535
+ for (const entry of readdirSync3(skillsDir)) {
1536
+ const full = join7(skillsDir, entry);
1307
1537
  if (!statSync2(full).isDirectory()) continue;
1308
1538
  if (baseNames.has(entry)) continue;
1309
1539
  if (apiDirNames.has(entry)) continue;
1310
- rmSync(full, { recursive: true, force: true });
1540
+ rmSync2(full, { recursive: true, force: true });
1311
1541
  removed.push(entry);
1312
1542
  }
1313
1543
  return { written, skipped, removed };
1314
1544
  }
1315
1545
 
1316
1546
  // src/rules-sync.ts
1317
- var MANIFEST_FILE2 = ".rules-sync-manifest.json";
1547
+ var MANIFEST_FILE3 = ".rules-sync-manifest.json";
1318
1548
  function ruleLocalFileName(ruleName) {
1319
1549
  const trimmed = ruleName.trim();
1320
1550
  if (!trimmed) return "rule.md";
@@ -1323,13 +1553,13 @@ function ruleLocalFileName(ruleName) {
1323
1553
  return `${sanitized}.md`;
1324
1554
  }
1325
1555
  function loadManifest2(rulesDir) {
1326
- const path10 = join7(rulesDir, MANIFEST_FILE2);
1327
- if (!existsSync5(toFsPath(path10))) {
1556
+ const path10 = join8(rulesDir, MANIFEST_FILE3);
1557
+ if (!existsSync6(toFsPath(path10))) {
1328
1558
  return { version: 1, rules: {} };
1329
1559
  }
1330
1560
  try {
1331
1561
  const parsed = JSON.parse(
1332
- readFileSync5(toFsPath(path10), "utf8")
1562
+ readFileSync6(toFsPath(path10), "utf8")
1333
1563
  );
1334
1564
  if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
1335
1565
  return parsed;
@@ -1339,8 +1569,8 @@ function loadManifest2(rulesDir) {
1339
1569
  return { version: 1, rules: {} };
1340
1570
  }
1341
1571
  function saveManifest2(rulesDir, manifest) {
1342
- writeFileSync7(
1343
- toFsPath(join7(rulesDir, MANIFEST_FILE2)),
1572
+ writeFileSync8(
1573
+ toFsPath(join8(rulesDir, MANIFEST_FILE3)),
1344
1574
  `${JSON.stringify(manifest, null, 2)}
1345
1575
  `,
1346
1576
  "utf8"
@@ -1350,18 +1580,18 @@ function isBaseRuleFileName(fileName) {
1350
1580
  return listBaseRuleFileNames().includes(basename2(fileName));
1351
1581
  }
1352
1582
  function isRuleUpToDate(entry, rule, dest) {
1353
- if (!entry || !existsSync5(toFsPath(dest))) return false;
1583
+ if (!entry || !existsSync6(toFsPath(dest))) return false;
1354
1584
  if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
1355
1585
  const updatedAt = rule.updatedAt ?? "";
1356
1586
  if (entry.updatedAt !== updatedAt) return false;
1357
- const localContent = readFileSync5(toFsPath(dest), "utf8");
1587
+ const localContent = readFileSync6(toFsPath(dest), "utf8");
1358
1588
  return localContent === (rule.content ?? "");
1359
1589
  }
1360
1590
  async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
1361
1591
  const api = createApmApiClient(cfg);
1362
1592
  const baseline = await api.cli.branchBaseline({ sessionId, workdirPath });
1363
1593
  const repositoryId = baseline.repositoryId;
1364
- const rulesDir = join7(apmRoot ?? workspaceApmDir(workdirPath), "rules");
1594
+ const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
1365
1595
  await ensureDirExists(rulesDir);
1366
1596
  if (!repositoryId) {
1367
1597
  console.log(
@@ -1378,7 +1608,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
1378
1608
  for (const rule of list) {
1379
1609
  remoteIds.add(rule.id);
1380
1610
  const fileName = ruleLocalFileName(rule.name);
1381
- const dest = join7(rulesDir, fileName);
1611
+ const dest = join8(rulesDir, fileName);
1382
1612
  const entry = manifest.rules[rule.id];
1383
1613
  const updatedAt = rule.updatedAt ?? "";
1384
1614
  if (isRuleUpToDate(entry, rule, dest)) {
@@ -1387,7 +1617,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
1387
1617
  console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
1388
1618
  continue;
1389
1619
  }
1390
- writeFileSync7(toFsPath(dest), rule.content ?? "", "utf8");
1620
+ writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
1391
1621
  nextManifest.rules[rule.id] = { fileName, updatedAt };
1392
1622
  written.push(fileName);
1393
1623
  console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
@@ -1396,9 +1626,9 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
1396
1626
  for (const [ruleId, entry] of Object.entries(manifest.rules)) {
1397
1627
  if (remoteIds.has(ruleId)) continue;
1398
1628
  if (isBaseRuleFileName(entry.fileName)) continue;
1399
- const dest = join7(rulesDir, entry.fileName);
1400
- if (existsSync5(toFsPath(dest))) {
1401
- rmSync2(toFsPath(dest), { force: true });
1629
+ const dest = join8(rulesDir, entry.fileName);
1630
+ if (existsSync6(toFsPath(dest))) {
1631
+ rmSync3(toFsPath(dest), { force: true });
1402
1632
  }
1403
1633
  removed.push(entry.fileName);
1404
1634
  console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
@@ -1407,218 +1637,6 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
1407
1637
  return { written, skipped, removed, repositoryId };
1408
1638
  }
1409
1639
 
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
-
1622
1640
  // src/commands/pull.ts
1623
1641
  async function runPull(sessionId, remoteWorkdir) {
1624
1642
  const trimmedId = sessionId.trim();
@@ -2961,8 +2979,13 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
2961
2979
  };
2962
2980
  try {
2963
2981
  if (signal.aborted) return;
2964
- assertWorkspaceApmDirExists(workdir);
2965
- assertApmGitignoredInRepo(workdir);
2982
+ const { didInit } = await runStep(
2983
+ "workspace-init",
2984
+ () => ensureWorkspaceInitialized(workdir)
2985
+ );
2986
+ if (!didInit) {
2987
+ assertApmGitignoredInRepo(workdir);
2988
+ }
2966
2989
  await runStep(
2967
2990
  "status-typing",
2968
2991
  () => updateMessageStatus(cfg, messageId, "TYPING")
@@ -3006,6 +3029,12 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
3006
3029
  console.log(`[apm] step=update-skills skipped workdir=${workdir}`);
3007
3030
  }
3008
3031
  if (signal.aborted) return;
3032
+ if (!pullRan) {
3033
+ await runStep(
3034
+ "sync-project-documents-pull",
3035
+ () => syncRepositoryProjectDocumentsPull(workdir, apmRoot)
3036
+ );
3037
+ }
3009
3038
  await runStep(
3010
3039
  "cursor-agent",
3011
3040
  () => runCursorAgent(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.58",
3
+ "version": "6.0.60",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
File without changes