ai-project-manage-cli 7.1.36 → 7.1.38

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.
@@ -600,84 +600,9 @@ async function pushCurrentBranch(cwd) {
600
600
  }
601
601
  return true;
602
602
  }
603
- var GITIGNORE_COMMIT_MESSAGE = "chore(apm): ignore .apm directory";
604
- async function commitAndPushGitignore(workdir) {
605
- if (!await isGitRepo(workdir)) {
606
- console.log("[apm] \u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8BF7\u624B\u52A8\u63D0\u4EA4 .gitignore");
607
- return;
608
- }
609
- await execGit(workdir, ["add", "--", ".gitignore"]);
610
- await execGit(workdir, ["commit", "-m", GITIGNORE_COMMIT_MESSAGE]);
611
- console.log(`[apm] \u5DF2\u63D0\u4EA4 .gitignore: ${GITIGNORE_COMMIT_MESSAGE}`);
612
- const pushed = await pushCurrentBranch(workdir);
613
- if (pushed) {
614
- console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
615
- } else {
616
- console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
617
- }
618
- }
619
-
620
- // src/baseline-resolve.ts
621
- function formatBaselineDiagnostic(workdirPath, baselineWorkdirPath, diagnostic) {
622
- return diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baselineWorkdirPath}\uFF09
623
- \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
624
- }
625
- async function matchRepositoryByGitRemote(api, workdirPath) {
626
- const gitRoot = await resolveGitRepoRoot(workdirPath);
627
- const gitUrl = await tryReadGitOriginUrl(gitRoot);
628
- if (!gitUrl) {
629
- return null;
630
- }
631
- const matched = await api.cli.matchRepository({ url: gitUrl });
632
- const repositoryId = matched.repositoryId?.trim();
633
- const defaultBranch = matched.defaultBranch?.trim();
634
- if (!repositoryId || !defaultBranch) {
635
- return null;
636
- }
637
- console.log(
638
- `[apm] \u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u672A\u5339\u914D\uFF0C\u5DF2\u901A\u8FC7 git remote \u5173\u8054\u4ED3\u5E93: ${gitUrl}`
639
- );
640
- return { repositoryId, defaultBranch };
641
- }
642
- async function resolveWorkspaceBaseline(api, workdirPath) {
643
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
644
- const repositoryId = baseline.repositoryId?.trim();
645
- const defaultBranch = baseline.defaultBranch?.trim();
646
- if (repositoryId && defaultBranch) {
647
- return {
648
- repositoryId,
649
- defaultBranch,
650
- workdirPath: baseline.workdirPath,
651
- matchedViaGitRemote: false
652
- };
653
- }
654
- const viaGit = await matchRepositoryByGitRemote(api, workdirPath);
655
- if (viaGit) {
656
- return {
657
- ...viaGit,
658
- workdirPath: baseline.workdirPath,
659
- matchedViaGitRemote: true
660
- };
661
- }
662
- throw new Error(
663
- `[apm] ${formatBaselineDiagnostic(
664
- workdirPath,
665
- baseline.workdirPath,
666
- baseline.diagnostic
667
- )}`
668
- );
669
- }
670
603
 
671
604
  // src/command-utils.ts
672
- import {
673
- copyFileSync,
674
- existsSync,
675
- mkdirSync as mkdirSync2,
676
- readFileSync as readFileSync2,
677
- readdirSync,
678
- statSync,
679
- writeFileSync as writeFileSync2
680
- } from "fs";
605
+ import { copyFileSync, existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
681
606
  import { basename, dirname, extname, join as join2, resolve as resolve2 } from "path";
682
607
  import { fileURLToPath } from "url";
683
608
  var __dirname = dirname(fileURLToPath(import.meta.url));
@@ -699,63 +624,6 @@ function isWorkspaceApmInitialized(workdir) {
699
624
  }
700
625
  return readdirSync(fsApmDir).length > 0;
701
626
  }
702
- var APM_GITIGNORE_PATTERNS = [
703
- /^\.apm\/?$/,
704
- /^\.apm\/\*\*$/,
705
- /^\*\*\/\.apm\/?$/,
706
- /^\*\*\/\.apm\/\*\*$/,
707
- /^\/\.apm\/?$/,
708
- /^\/\.apm\/\*\*$/
709
- ];
710
- function normalizeGitignorePattern(line) {
711
- const trimmed = line.trim();
712
- if (!trimmed || trimmed.startsWith("#")) return "";
713
- if (trimmed.startsWith("!")) return "";
714
- const hashIndex = trimmed.indexOf("#");
715
- return (hashIndex >= 0 ? trimmed.slice(0, hashIndex) : trimmed).trim();
716
- }
717
- function gitignoreIgnoresApm(line) {
718
- const pattern = normalizeGitignorePattern(line);
719
- if (!pattern) return false;
720
- return APM_GITIGNORE_PATTERNS.some((re) => re.test(pattern));
721
- }
722
- var APM_GITIGNORE_LINE = "**/.apm/**";
723
- function ensureApmGitignoredInRepo(workdir) {
724
- const gitignorePath = join2(workdir, ".gitignore");
725
- const fsGitignorePath = toFsPath(gitignorePath);
726
- if (!existsSync(fsGitignorePath)) {
727
- writeFileSync2(fsGitignorePath, `${APM_GITIGNORE_LINE}
728
- `, "utf8");
729
- return true;
730
- }
731
- const content = readFileSync2(fsGitignorePath, "utf8");
732
- if (content.split(/\r?\n/).some(gitignoreIgnoresApm)) {
733
- return false;
734
- }
735
- const suffix = content.endsWith("\n") || content.length === 0 ? "" : "\n";
736
- writeFileSync2(
737
- fsGitignorePath,
738
- `${content}${suffix}${APM_GITIGNORE_LINE}
739
- `,
740
- "utf8"
741
- );
742
- return true;
743
- }
744
- function assertApmGitignoredInRepo(workdir) {
745
- const gitignorePath = join2(workdir, ".gitignore");
746
- const fsGitignorePath = toFsPath(gitignorePath);
747
- if (!existsSync(fsGitignorePath)) {
748
- throw new Error(
749
- `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u7F3A\u5C11 .gitignore \u6587\u4EF6\uFF0C\u8BF7\u6DFB\u52A0\u5BF9 .apm \u7684\u5FFD\u7565\u89C4\u5219\uFF08\u4F8B\u5982 **/.apm/**\uFF09\uFF0C\u907F\u514D\u672C\u5730\u4F1A\u8BDD\u4E0E\u90E8\u7F72\u51ED\u636E\u88AB\u63D0\u4EA4\u3002`
750
- );
751
- }
752
- const content = readFileSync2(fsGitignorePath, "utf8");
753
- if (!content.split(/\r?\n/).some(gitignoreIgnoresApm)) {
754
- throw new Error(
755
- `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u7684 .gitignore \u672A\u5FFD\u7565 .apm\uFF0C\u8BF7\u6DFB\u52A0 **/.apm/** \u6216 .apm/\uFF0C\u907F\u514D\u672C\u5730\u4F1A\u8BDD\u4E0E\u90E8\u7F72\u51ED\u636E\u88AB\u63D0\u4EA4\u3002`
756
- );
757
- }
758
- }
759
627
  var SESSION_ATTACHMENTS_SUBDIR = "attachments";
760
628
  var WEBIDE_SUBDIR = "webide";
761
629
  function webideTaskDir(taskId, workdir) {
@@ -780,19 +648,10 @@ async function ensureDirExists(dir) {
780
648
  async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
781
649
  const dir = workspaceApmDir(cwd);
782
650
  const fsDir = toFsPath(dir);
783
- if (!existsSync(fsDir)) {
784
- mkdirSync2(fsDir, { recursive: true });
785
- return;
786
- }
787
- const st = statSync(fsDir);
788
- if (!st.isDirectory()) {
651
+ if (existsSync(fsDir) && !statSync(fsDir).isDirectory()) {
789
652
  throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${dir}`);
790
653
  }
791
- if (readdirSync(fsDir).length > 0) {
792
- throw new Error(
793
- "[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"
794
- );
795
- }
654
+ mkdirSync2(fsDir, { recursive: true });
796
655
  }
797
656
  var WORKSPACE_TEMPLATE_SUBDIRS = [
798
657
  "sessions",
@@ -872,10 +731,10 @@ async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
872
731
  import {
873
732
  existsSync as existsSync2,
874
733
  mkdirSync as mkdirSync3,
875
- readFileSync as readFileSync3,
734
+ readFileSync as readFileSync2,
876
735
  readdirSync as readdirSync2,
877
736
  statSync as statSync2,
878
- writeFileSync as writeFileSync3
737
+ writeFileSync as writeFileSync2
879
738
  } from "fs";
880
739
  import { basename as basename2, dirname as dirname2, join as join3, relative, resolve as resolve3 } from "path";
881
740
  var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
@@ -923,7 +782,7 @@ function readManifest(workdir) {
923
782
  }
924
783
  try {
925
784
  const raw = JSON.parse(
926
- readFileSync3(path, "utf8")
785
+ readFileSync2(path, "utf8")
927
786
  );
928
787
  if (raw?.version !== WORKSPACE_REPOS_VERSION) {
929
788
  return null;
@@ -956,7 +815,7 @@ function writeManifest(workdir, manifest) {
956
815
  const apmDir = toFsPath(workspaceApmDir(workdir));
957
816
  mkdirSync3(apmDir, { recursive: true });
958
817
  const path = toFsPath(manifestPath(workdir));
959
- writeFileSync3(path, `${JSON.stringify(manifest, null, 2)}
818
+ writeFileSync2(path, `${JSON.stringify(manifest, null, 2)}
960
819
  `, "utf8");
961
820
  }
962
821
  function manifestStillValid(workdir, manifest) {
@@ -1576,7 +1435,7 @@ ${JSON.stringify(event, null, 2)}
1576
1435
  }
1577
1436
 
1578
1437
  // src/commands/connect/agent-session-registry.ts
1579
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
1438
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
1580
1439
  import { dirname as dirname3, resolve as resolve4 } from "node:path";
1581
1440
  function registryPath(workdir, sessionId) {
1582
1441
  return resolve4(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
@@ -1586,7 +1445,7 @@ function readRegistry(path) {
1586
1445
  return {};
1587
1446
  }
1588
1447
  try {
1589
- const parsed = JSON.parse(readFileSync4(path, "utf8"));
1448
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
1590
1449
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1591
1450
  const result = {};
1592
1451
  for (const [key, value] of Object.entries(
@@ -1604,7 +1463,7 @@ function readRegistry(path) {
1604
1463
  }
1605
1464
  function writeRegistry(path, registry) {
1606
1465
  mkdirSync4(dirname3(path), { recursive: true });
1607
- writeFileSync4(path, `${JSON.stringify(registry, null, 2)}
1466
+ writeFileSync3(path, `${JSON.stringify(registry, null, 2)}
1608
1467
  `, "utf8");
1609
1468
  }
1610
1469
  function loadSessionAgentId(workdir, sessionId, user) {
@@ -2536,7 +2395,7 @@ async function runCursorAgent(cfg, ctx, options) {
2536
2395
  }
2537
2396
 
2538
2397
  // src/commands/connect/webide-agent-registry.ts
2539
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
2398
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
2540
2399
  import { dirname as dirname4, resolve as resolve5 } from "node:path";
2541
2400
  function registryPath2(workdir, taskId) {
2542
2401
  return resolve5(workdir, ".apm", "webide", taskId, "cursor-agent.json");
@@ -2546,7 +2405,7 @@ function readRegistry2(path) {
2546
2405
  return {};
2547
2406
  }
2548
2407
  try {
2549
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
2408
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
2550
2409
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2551
2410
  const raw = parsed;
2552
2411
  const state = {};
@@ -2576,7 +2435,7 @@ function readRegistry2(path) {
2576
2435
  }
2577
2436
  function writeRegistry2(path, registry) {
2578
2437
  mkdirSync5(dirname4(path), { recursive: true });
2579
- writeFileSync5(path, `${JSON.stringify(registry, null, 2)}
2438
+ writeFileSync4(path, `${JSON.stringify(registry, null, 2)}
2580
2439
  `, "utf8");
2581
2440
  }
2582
2441
  function syncWebIdeTaskState(workdir, taskId, patch) {
@@ -3013,463 +2872,111 @@ function resolveMessageReplyFallback(fallback) {
3013
2872
  }
3014
2873
 
3015
2874
  // src/commands/init.ts
3016
- import { join as join6 } from "path";
3017
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "fs";
3018
-
3019
- // src/deployment-config-sync.ts
3020
2875
  import { join as join4 } from "path";
3021
- import { writeFileSync as writeFileSync6 } from "fs";
3022
- var TEMPLATE_HINT = "\u4FDD\u7559\u6A21\u677F .apm/apm.config.json";
3023
- var SYNC_HINT = "\u767B\u8BB0\u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u3001\u7ED1\u5B9A\u4ED3\u5E93\u540E\uFF0C\u53EF\u6267\u884C: apm sync-deploy-config";
3024
- async function resolveRepositoryIdForSync(api, workdirPath) {
3025
- try {
3026
- const baseline = await resolveWorkspaceBaseline(api, workdirPath);
3027
- return { repositoryId: baseline.repositoryId, diagnostic: null };
3028
- } catch (err) {
3029
- const detail = err instanceof Error ? err.message : String(err);
3030
- return {
3031
- repositoryId: null,
3032
- diagnostic: detail.replace(/^\[apm\]\s*/, "")
3033
- };
3034
- }
3035
- }
3036
- async function writeDeploymentConfigContent(apmDir, content, configName) {
3037
- let parsed;
3038
- try {
3039
- parsed = JSON.parse(content);
3040
- } catch {
3041
- console.warn(
3042
- `[apm] \u8FDC\u7A0B\u90E8\u7F72\u914D\u7F6E\u300C${configName}\u300DJSON \u65E0\u6548\uFF08${TEMPLATE_HINT}\uFF09`
2876
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
2877
+ async function initializeWorkspace(workdir, options) {
2878
+ await ensureWorkspaceApmDirForInit(workdir);
2879
+ const apmDir = workspaceApmDir(workdir);
2880
+ await copyTemplateFiles(apmDir, workdir);
2881
+ const trimmedName = options?.name?.trim();
2882
+ if (trimmedName) {
2883
+ const apmConfigPath = toFsPath(join4(apmDir, "apm.config.json"));
2884
+ const config = readFileSync5(apmConfigPath, "utf8");
2885
+ const configJson = JSON.parse(config);
2886
+ configJson.name = trimmedName;
2887
+ writeFileSync5(
2888
+ apmConfigPath,
2889
+ `${JSON.stringify(configJson, null, 2)}
2890
+ `,
2891
+ "utf8"
3043
2892
  );
3044
- return false;
3045
2893
  }
3046
- const apmConfigPath = toFsPath(join4(apmDir, "apm.config.json"));
3047
- writeFileSync6(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
3048
- `, "utf8");
3049
- console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${configName}`);
3050
- console.log("[apm] \u5DF2\u5199\u5165 .apm/apm.config.json");
3051
- return true;
2894
+ console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
2895
+ return { didInit: true };
3052
2896
  }
3053
- async function syncRemoteDeploymentConfig(workdirPath, apmDir, options) {
3054
- const cfg = await tryReadApmConfig();
3055
- if (!cfg || !resolveApiKey(cfg)) {
3056
- console.log(
3057
- `[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E\u540C\u6B65\uFF08${TEMPLATE_HINT}\uFF09\u3002
3058
- [apm] \u8BF7\u5148\u6267\u884C apm login\uFF0C\u518D\u6267\u884C apm sync-deploy-config \u62C9\u53D6\u6700\u65B0\u914D\u7F6E\u3002`
3059
- );
3060
- return { synced: false, repositoryId: null };
3061
- }
3062
- const api = createApmApiClient(cfg);
3063
- const preferredId = options?.repositoryId?.trim() || "";
3064
- let repositoryId = preferredId || null;
3065
- let diagnostic = null;
3066
- if (!repositoryId) {
3067
- const resolved = await resolveRepositoryIdForSync(api, workdirPath);
3068
- repositoryId = resolved.repositoryId;
3069
- diagnostic = resolved.diagnostic;
3070
- }
3071
- if (!repositoryId) {
3072
- console.log(
3073
- `[apm] \u672A\u80FD\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E\uFF08${TEMPLATE_HINT}\uFF09\u3002
3074
- ${diagnostic ?? ""}
3075
- [apm] ${SYNC_HINT}`
3076
- );
3077
- return { synced: false, repositoryId: null };
3078
- }
3079
- const { config } = await api.cli.getDeploymentConfiguration({ repositoryId });
3080
- if (!config) {
3081
- console.log(
3082
- `[apm] \u672A\u627E\u5230\u5173\u8054\u4ED3\u5E93\u7684\u90E8\u7F72\u914D\u7F6E\uFF08${TEMPLATE_HINT}\uFF0CrepositoryId\uFF1A${repositoryId}\uFF09\u3002
3083
- [apm] \u8BF7\u5728\u5E73\u53F0\u300C\u90E8\u7F72\u914D\u7F6E\u300D\u4E2D\u521B\u5EFA\u914D\u7F6E\u5E76\u5173\u8054\u8BE5\u4ED3\u5E93\uFF0C\u7136\u540E\u6267\u884C: apm sync-deploy-config`
3084
- );
3085
- return { synced: false, repositoryId };
2897
+ async function ensureWorkspaceInitialized(workdir, options) {
2898
+ if (isWorkspaceApmInitialized(workdir)) {
2899
+ return { didInit: false };
3086
2900
  }
3087
- const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
3088
- const wrote = await writeDeploymentConfigContent(
3089
- targetApmDir,
3090
- config.content,
3091
- config.name
2901
+ console.log(
2902
+ `[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`
3092
2903
  );
3093
- if (!wrote) {
3094
- return { synced: false, repositoryId };
3095
- }
3096
- return { synced: true, repositoryId, configName: config.name };
2904
+ return initializeWorkspace(workdir, options);
3097
2905
  }
3098
2906
 
3099
- // src/project-documents-sync.ts
3100
- import {
3101
- existsSync as existsSync6,
3102
- readdirSync as readdirSync3,
3103
- readFileSync as readFileSync6,
3104
- rmSync as rmSync2,
3105
- writeFileSync as writeFileSync7
3106
- } from "fs";
3107
- import { createHash } from "crypto";
3108
- import { dirname as dirname5, join as join5, relative as relative2, sep } from "path";
3109
- var MANIFEST_FILE = "manifest.json";
3110
- function normalizeProjectIdForPath(projectId) {
3111
- const id = projectId.trim();
3112
- if (!id) {
3113
- throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
3114
- }
3115
- if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
3116
- throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
3117
- }
3118
- return id;
2907
+ // src/commands/connect/pre-step-cache.ts
2908
+ function sessionWorkdirKey(sessionId, workdir) {
2909
+ return `${sessionId}\0${workdir}`;
3119
2910
  }
3120
- function projectDocumentsDir(apmRoot, projectId) {
3121
- const id = normalizeProjectIdForPath(projectId);
3122
- return join5(apmRoot ?? workspaceApmDir(), "project", id);
2911
+ var lastBranchKey = null;
2912
+ function shouldRunBranch(sessionId, workdir) {
2913
+ return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
3123
2914
  }
3124
- function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
3125
- const normalized = normalizeLocalDocumentPath(documentPath);
3126
- return join5(
3127
- projectDocumentsDir(apmRoot, projectId),
3128
- ...normalized.split("/")
3129
- );
2915
+ function markBranchDone(sessionId, workdir) {
2916
+ lastBranchKey = sessionWorkdirKey(sessionId, workdir);
3130
2917
  }
3131
- function normalizeLocalDocumentPath(path) {
3132
- const trimmed = path.trim().replace(/\\/g, "/");
3133
- if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
3134
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path}`);
3135
- }
3136
- const segments = trimmed.split("/").filter(Boolean);
3137
- if (segments.some((segment) => segment === ".." || segment === ".")) {
3138
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path}`);
3139
- }
3140
- return segments.join("/");
2918
+
2919
+ // src/commands/connect/webide-pull-requests.ts
2920
+ var PRE_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set([
2921
+ "generate-cases",
2922
+ "run-auto-test",
2923
+ // 验收合并前兜底确保 PR 存在
2924
+ "accept"
2925
+ ]);
2926
+ var POST_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set(["create-pr", "finish-manual-test"]);
2927
+ function shouldEnsureWebIdePullRequests(action) {
2928
+ return PRE_AGENT_PR_ACTIONS.has(action);
3141
2929
  }
3142
- function hashLocalFileContent(content) {
3143
- return createHash("sha256").update(content, "utf8").digest("hex");
2930
+ function shouldEnsureWebIdePullRequestsAfterAgent(action) {
2931
+ return POST_AGENT_PR_ACTIONS.has(action);
3144
2932
  }
3145
- function readLocalManifest(apmRoot, projectId) {
3146
- const manifestPath3 = join5(
3147
- projectDocumentsDir(apmRoot, projectId),
3148
- MANIFEST_FILE
2933
+ function formatWebIdePullRequestSummary(result) {
2934
+ const lines = [];
2935
+ lines.push("## PR \u521B\u5EFA\u7ED3\u679C");
2936
+ lines.push(
2937
+ `\u5408\u8BA1\uFF1A\u6210\u529F ${result.ensured} \u6761\uFF0C\u65E0\u53D8\u66F4\u8DF3\u8FC7 ${result.skippedNoChanges} \u6761\uFF0C\u5176\u4ED6\u8DF3\u8FC7 ${result.skipped} \u6761\uFF0C\u5931\u8D25 ${result.failed} \u6761\u3002`
3149
2938
  );
3150
- if (!existsSync6(manifestPath3)) {
3151
- return null;
3152
- }
3153
- try {
3154
- return JSON.parse(
3155
- readFileSync6(manifestPath3, "utf8")
3156
- );
3157
- } catch {
3158
- return null;
3159
- }
3160
- }
3161
- function listLocalDocumentPaths(apmRoot, projectId) {
3162
- const root = projectDocumentsDir(apmRoot, projectId);
3163
- if (!existsSync6(root)) {
3164
- return [];
2939
+ if (result.items.length === 0) {
2940
+ lines.push("\u672A\u5904\u7406\u4EFB\u4F55\u4ED3\u5E93\u3002");
2941
+ return lines.join("\n");
3165
2942
  }
3166
- const paths = [];
3167
- const walk = (dir) => {
3168
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
3169
- const abs = join5(dir, entry.name);
3170
- if (entry.isDirectory()) {
3171
- walk(abs);
3172
- continue;
3173
- }
3174
- if (entry.isFile() && entry.name === MANIFEST_FILE) {
3175
- continue;
3176
- }
3177
- const rel = relative2(root, abs).split(sep).join("/");
3178
- paths.push(rel);
2943
+ lines.push("");
2944
+ for (const item of result.items) {
2945
+ if (item.outcome === "created") {
2946
+ const num = item.number != null && item.number > 0 ? `#${item.number}` : "\uFF08\u65E0\u7F16\u53F7\uFF09";
2947
+ const branch = item.headBranch && item.baseBranch ? `${item.headBranch} \u2192 ${item.baseBranch}` : item.baseBranch ? `\u76EE\u6807\u5206\u652F ${item.baseBranch}` : "";
2948
+ const state = item.state ? `\u72B6\u6001 ${item.state}` : "";
2949
+ const url = item.url?.trim() ? item.url.trim() : "";
2950
+ lines.push(
2951
+ `- **${item.label}**\uFF1A\u5DF2\u521B\u5EFA ${num}${branch ? `\uFF0C${branch}` : ""}${state ? `\uFF0C${state}` : ""}${url ? `\uFF0C${url}` : ""}`
2952
+ );
2953
+ continue;
3179
2954
  }
3180
- };
3181
- walk(root);
3182
- return paths.sort();
3183
- }
3184
- function diffManifestPaths(remote, local) {
3185
- const remoteMap = new Map(
3186
- (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3187
- );
3188
- const localMap = new Map(
3189
- (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3190
- );
3191
- const download = [];
3192
- for (const [path, hash] of remoteMap) {
3193
- if (localMap.get(path) !== hash) {
3194
- download.push(path);
2955
+ if (item.outcome === "skipped_no_changes") {
2956
+ lines.push(
2957
+ `- **${item.label}**\uFF1A\u76F8\u5BF9\u57FA\u7EBF ${item.baseBranch || "\uFF08\u672A\u77E5\uFF09"} \u65E0\u4EE3\u7801\u53D8\u66F4\uFF0C\u672A\u521B\u5EFA PR`
2958
+ );
2959
+ continue;
3195
2960
  }
3196
- }
3197
- const deleteLocal = [];
3198
- for (const path of localMap.keys()) {
3199
- if (!remoteMap.has(path)) {
3200
- deleteLocal.push(path);
2961
+ if (item.outcome === "skipped") {
2962
+ lines.push(`- **${item.label}**\uFF1A\u5DF2\u8DF3\u8FC7 \u2014 ${item.detail}`);
2963
+ continue;
3201
2964
  }
2965
+ lines.push(`- **${item.label}**\uFF1A\u521B\u5EFA\u5931\u8D25 \u2014 ${item.detail}`);
3202
2966
  }
3203
- return { download, deleteLocal };
2967
+ return lines.join("\n");
3204
2968
  }
3205
- async function resolveProjectIdForSync(api, workdirPath, options) {
3206
- const explicit = options?.projectId?.trim();
3207
- if (explicit) {
3208
- return { projectId: explicit, diagnostic: null };
3209
- }
3210
- try {
3211
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
3212
- const projectId = baseline.projectId?.trim() || null;
3213
- if (projectId) {
3214
- return { projectId, diagnostic: null };
3215
- }
3216
- const projectIds = baseline.projectIds ?? [];
3217
- if (projectIds.length === 0) {
3218
- return {
3219
- projectId: null,
3220
- diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
3221
- \u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
3222
- };
3223
- }
3224
- return {
3225
- projectId: null,
3226
- diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
3227
- \u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
3228
- };
3229
- } catch (err) {
3230
- const detail = err instanceof Error ? err.message : String(err);
3231
- return {
3232
- projectId: null,
3233
- diagnostic: detail.replace(/^\[apm\]\s*/, "")
3234
- };
3235
- }
3236
- }
3237
- async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
3238
- const empty = {
3239
- synced: false,
3240
- projectId: null,
3241
- downloaded: 0,
3242
- deleted: 0
3243
- };
3244
- const cfg = await tryReadApmConfig();
3245
- if (!cfg || !resolveApiKey(cfg)) {
3246
- console.log(
3247
- "[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
3248
- );
3249
- return empty;
3250
- }
3251
- const api = createApmApiClient(cfg);
3252
- const { projectId, diagnostic } = await resolveProjectIdForSync(
3253
- api,
3254
- workdirPath,
3255
- options
3256
- );
3257
- if (!projectId) {
3258
- console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
3259
- ${diagnostic ?? ""}`);
3260
- return empty;
3261
- }
3262
- const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
3263
- const docsDir = projectDocumentsDir(targetApmDir, projectId);
3264
- await ensureDirExists(docsDir);
3265
- const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
3266
- { projectId }
3267
- );
3268
- if (!remoteManifest) {
3269
- console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
3270
- return { ...empty, projectId };
3271
- }
3272
- const localManifest = readLocalManifest(targetApmDir, projectId);
3273
- const { download, deleteLocal } = diffManifestPaths(
3274
- remoteManifest,
3275
- localManifest
3276
- );
3277
- let downloaded = 0;
3278
- if (download.length > 0) {
3279
- const { list } = await api.cli.listProjectDocuments({
3280
- projectId,
3281
- paths: download.join(",")
3282
- });
3283
- for (const doc of list) {
3284
- const absPath = toFsPath(
3285
- projectDocumentLocalPath(targetApmDir, projectId, doc.path)
3286
- );
3287
- await ensureDirExists(dirname5(absPath));
3288
- writeFileSync7(absPath, doc.content, "utf8");
3289
- downloaded += 1;
3290
- }
3291
- }
3292
- let deleted = 0;
3293
- for (const path of deleteLocal) {
3294
- const absPath = toFsPath(
3295
- projectDocumentLocalPath(targetApmDir, projectId, path)
3296
- );
3297
- if (existsSync6(absPath)) {
3298
- rmSync2(absPath, { force: true });
3299
- deleted += 1;
3300
- }
3301
- }
3302
- writeFileSync7(
3303
- toFsPath(join5(docsDir, MANIFEST_FILE)),
3304
- `${JSON.stringify(remoteManifest, null, 2)}
3305
- `,
3306
- "utf8"
3307
- );
3308
- console.log(
3309
- `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
3310
- );
3311
- return {
3312
- synced: true,
3313
- projectId,
3314
- downloaded,
3315
- deleted
3316
- };
3317
- }
3318
- async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
3319
- const api = createApmApiClient(cfg);
3320
- const { projectId } = await resolveProjectIdForSync(
3321
- api,
3322
- workdirPath,
3323
- options
3324
- );
3325
- if (!projectId) {
3326
- return 0;
3327
- }
3328
- const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
3329
- const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
3330
- if (localPaths.length === 0) {
3331
- console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3332
- return 0;
3333
- }
3334
- const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
3335
- const remoteHashByPath = new Map(
3336
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3337
- );
3338
- const remoteDescriptionByPath = new Map(
3339
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
3340
- );
3341
- let synced = 0;
3342
- for (const path of localPaths) {
3343
- const absPath = toFsPath(
3344
- projectDocumentLocalPath(targetApmDir, projectId, path)
3345
- );
3346
- const content = readFileSync6(absPath, "utf8");
3347
- const contentHash = hashLocalFileContent(content);
3348
- if (remoteHashByPath.get(path) === contentHash) {
3349
- continue;
3350
- }
3351
- await api.cli.upsertProjectDocument({
3352
- projectId,
3353
- path,
3354
- content,
3355
- description: remoteDescriptionByPath.get(path) ?? void 0
3356
- });
3357
- synced += 1;
3358
- console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path}`);
3359
- }
3360
- if (synced === 0) {
3361
- console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3362
- }
3363
- return synced;
3364
- }
3365
-
3366
- // src/commands/init.ts
3367
- async function ensureWorkspaceInitialized(workdir, options) {
3368
- if (isWorkspaceApmInitialized(workdir)) {
3369
- return { didInit: false };
3370
- }
3371
- console.log(
3372
- `[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`
3373
- );
3374
- await ensureWorkspaceApmDirForInit(workdir);
3375
- if (ensureApmGitignoredInRepo(workdir)) {
3376
- console.log("[apm] \u5DF2\u5728 .gitignore \u4E2D\u6DFB\u52A0 **/.apm/**");
3377
- await commitAndPushGitignore(workdir);
3378
- }
3379
- const apmDir = workspaceApmDir(workdir);
3380
- await copyTemplateFiles(apmDir, workdir);
3381
- const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
3382
- await syncProjectDocumentsPull(workdir, apmDir);
3383
- const trimmedName = options?.name?.trim();
3384
- if (trimmedName) {
3385
- const apmConfigPath = toFsPath(join6(apmDir, "apm.config.json"));
3386
- const config = readFileSync7(apmConfigPath, "utf8");
3387
- const configJson = JSON.parse(config);
3388
- configJson.name = trimmedName;
3389
- writeFileSync8(
3390
- apmConfigPath,
3391
- `${JSON.stringify(configJson, null, 2)}
3392
- `,
3393
- "utf8"
3394
- );
3395
- }
3396
- console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
3397
- return { didInit: true, syncResult };
3398
- }
3399
-
3400
- // src/commands/connect/pre-step-cache.ts
3401
- function sessionWorkdirKey(sessionId, workdir) {
3402
- return `${sessionId}\0${workdir}`;
3403
- }
3404
- var lastBranchKey = null;
3405
- function shouldRunBranch(sessionId, workdir) {
3406
- return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
3407
- }
3408
- function markBranchDone(sessionId, workdir) {
3409
- lastBranchKey = sessionWorkdirKey(sessionId, workdir);
3410
- }
3411
-
3412
- // src/commands/connect/webide-pull-requests.ts
3413
- var PRE_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set([
3414
- "generate-cases",
3415
- "run-auto-test",
3416
- // 验收合并前兜底确保 PR 存在
3417
- "accept"
3418
- ]);
3419
- var POST_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set(["create-pr", "finish-manual-test"]);
3420
- function shouldEnsureWebIdePullRequests(action) {
3421
- return PRE_AGENT_PR_ACTIONS.has(action);
3422
- }
3423
- function shouldEnsureWebIdePullRequestsAfterAgent(action) {
3424
- return POST_AGENT_PR_ACTIONS.has(action);
3425
- }
3426
- function formatWebIdePullRequestSummary(result) {
3427
- const lines = [];
3428
- lines.push("## PR \u521B\u5EFA\u7ED3\u679C");
3429
- lines.push(
3430
- `\u5408\u8BA1\uFF1A\u6210\u529F ${result.ensured} \u6761\uFF0C\u65E0\u53D8\u66F4\u8DF3\u8FC7 ${result.skippedNoChanges} \u6761\uFF0C\u5176\u4ED6\u8DF3\u8FC7 ${result.skipped} \u6761\uFF0C\u5931\u8D25 ${result.failed} \u6761\u3002`
3431
- );
3432
- if (result.items.length === 0) {
3433
- lines.push("\u672A\u5904\u7406\u4EFB\u4F55\u4ED3\u5E93\u3002");
3434
- return lines.join("\n");
3435
- }
3436
- lines.push("");
3437
- for (const item of result.items) {
3438
- if (item.outcome === "created") {
3439
- const num = item.number != null && item.number > 0 ? `#${item.number}` : "\uFF08\u65E0\u7F16\u53F7\uFF09";
3440
- const branch = item.headBranch && item.baseBranch ? `${item.headBranch} \u2192 ${item.baseBranch}` : item.baseBranch ? `\u76EE\u6807\u5206\u652F ${item.baseBranch}` : "";
3441
- const state = item.state ? `\u72B6\u6001 ${item.state}` : "";
3442
- const url = item.url?.trim() ? item.url.trim() : "";
3443
- lines.push(
3444
- `- **${item.label}**\uFF1A\u5DF2\u521B\u5EFA ${num}${branch ? `\uFF0C${branch}` : ""}${state ? `\uFF0C${state}` : ""}${url ? `\uFF0C${url}` : ""}`
3445
- );
3446
- continue;
3447
- }
3448
- if (item.outcome === "skipped_no_changes") {
3449
- lines.push(
3450
- `- **${item.label}**\uFF1A\u76F8\u5BF9\u57FA\u7EBF ${item.baseBranch || "\uFF08\u672A\u77E5\uFF09"} \u65E0\u4EE3\u7801\u53D8\u66F4\uFF0C\u672A\u521B\u5EFA PR`
3451
- );
3452
- continue;
3453
- }
3454
- if (item.outcome === "skipped") {
3455
- lines.push(`- **${item.label}**\uFF1A\u5DF2\u8DF3\u8FC7 \u2014 ${item.detail}`);
3456
- continue;
3457
- }
3458
- lines.push(`- **${item.label}**\uFF1A\u521B\u5EFA\u5931\u8D25 \u2014 ${item.detail}`);
3459
- }
3460
- return lines.join("\n");
3461
- }
3462
- async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3463
- const api = createApmApiClient(cfg);
3464
- const empty = () => ({
3465
- ensured: 0,
3466
- skipped: 0,
3467
- skippedNoChanges: 0,
3468
- failed: 0,
3469
- items: [],
3470
- tips: []
3471
- });
3472
- let repoRoots;
2969
+ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
2970
+ const api = createApmApiClient(cfg);
2971
+ const empty = () => ({
2972
+ ensured: 0,
2973
+ skipped: 0,
2974
+ skippedNoChanges: 0,
2975
+ failed: 0,
2976
+ items: [],
2977
+ tips: []
2978
+ });
2979
+ let repoRoots;
3473
2980
  try {
3474
2981
  const manifest = loadWorkspaceReposCache(workdir);
3475
2982
  repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
@@ -3646,14 +3153,14 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3646
3153
  }
3647
3154
 
3648
3155
  // src/version.ts
3649
- import { readFileSync as readFileSync8 } from "fs";
3650
- import { dirname as dirname6, join as join7 } from "path";
3156
+ import { readFileSync as readFileSync6 } from "fs";
3157
+ import { dirname as dirname5, join as join5 } from "path";
3651
3158
  import { fileURLToPath as fileURLToPath2 } from "url";
3652
3159
  function readCliVersion() {
3653
3160
  try {
3654
- const dir = dirname6(fileURLToPath2(import.meta.url));
3655
- const pkgPath = join7(dir, "..", "package.json");
3656
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
3161
+ const dir = dirname5(fileURLToPath2(import.meta.url));
3162
+ const pkgPath = join5(dir, "..", "package.json");
3163
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
3657
3164
  return pkg.version ?? "0.0.0";
3658
3165
  } catch {
3659
3166
  return "0.0.0";
@@ -3661,46 +3168,46 @@ function readCliVersion() {
3661
3168
  }
3662
3169
 
3663
3170
  // src/commands/update-skills.ts
3664
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, statSync as statSync4 } from "fs";
3665
- import { join as join9 } from "path";
3171
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7, statSync as statSync4 } from "fs";
3172
+ import { join as join7 } from "path";
3666
3173
 
3667
3174
  // src/skills-sync.ts
3668
3175
  import {
3669
3176
  copyFileSync as copyFileSync2,
3670
3177
  cpSync,
3671
- existsSync as existsSync7,
3178
+ existsSync as existsSync6,
3672
3179
  mkdirSync as mkdirSync6,
3673
- readdirSync as readdirSync4,
3674
- rmSync as rmSync3,
3180
+ readdirSync as readdirSync3,
3181
+ rmSync as rmSync2,
3675
3182
  statSync as statSync3,
3676
- writeFileSync as writeFileSync9
3183
+ writeFileSync as writeFileSync6
3677
3184
  } from "fs";
3678
- import { join as join8 } from "path";
3679
- var AGENTS_TEMPLATE_PATH = join8(CLI_TEMPLATE_DIR, "AGENTS.md");
3680
- var BASE_SKILLS_TEMPLATE_DIR = join8(CLI_TEMPLATE_DIR, "skills");
3681
- var BASE_RULES_TEMPLATE_DIR = join8(CLI_TEMPLATE_DIR, "rules");
3185
+ import { join as join6 } from "path";
3186
+ var AGENTS_TEMPLATE_PATH = join6(CLI_TEMPLATE_DIR, "AGENTS.md");
3187
+ var BASE_SKILLS_TEMPLATE_DIR = join6(CLI_TEMPLATE_DIR, "skills");
3188
+ var BASE_RULES_TEMPLATE_DIR = join6(CLI_TEMPLATE_DIR, "rules");
3682
3189
  function sanitizeSkillDirName(name) {
3683
3190
  const trimmed = name.trim();
3684
3191
  if (!trimmed) return "skill";
3685
3192
  return trimmed.replace(/[/\\:*?"<>|]/g, "_");
3686
3193
  }
3687
3194
  function listBaseSkillDirNames() {
3688
- if (!existsSync7(BASE_SKILLS_TEMPLATE_DIR)) return [];
3689
- return readdirSync4(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
3690
- const path = join8(BASE_SKILLS_TEMPLATE_DIR, name);
3195
+ if (!existsSync6(BASE_SKILLS_TEMPLATE_DIR)) return [];
3196
+ return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
3197
+ const path = join6(BASE_SKILLS_TEMPLATE_DIR, name);
3691
3198
  return statSync3(path).isDirectory();
3692
3199
  });
3693
3200
  }
3694
3201
  function syncAgentsGuide(apmDir) {
3695
- if (!existsSync7(AGENTS_TEMPLATE_PATH)) return false;
3202
+ if (!existsSync6(AGENTS_TEMPLATE_PATH)) return false;
3696
3203
  mkdirSync6(apmDir, { recursive: true });
3697
- copyFileSync2(AGENTS_TEMPLATE_PATH, join8(apmDir, "AGENTS.md"));
3204
+ copyFileSync2(AGENTS_TEMPLATE_PATH, join6(apmDir, "AGENTS.md"));
3698
3205
  return true;
3699
3206
  }
3700
3207
  function listBaseRuleFileNames() {
3701
- if (!existsSync7(BASE_RULES_TEMPLATE_DIR)) return [];
3702
- return readdirSync4(BASE_RULES_TEMPLATE_DIR).filter((name) => {
3703
- const path = join8(BASE_RULES_TEMPLATE_DIR, name);
3208
+ if (!existsSync6(BASE_RULES_TEMPLATE_DIR)) return [];
3209
+ return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
3210
+ const path = join6(BASE_RULES_TEMPLATE_DIR, name);
3704
3211
  return statSync3(path).isFile();
3705
3212
  });
3706
3213
  }
@@ -3708,8 +3215,8 @@ function syncBaseRules(rulesDir) {
3708
3215
  mkdirSync6(rulesDir, { recursive: true });
3709
3216
  const names = listBaseRuleFileNames();
3710
3217
  for (const name of names) {
3711
- const src = join8(BASE_RULES_TEMPLATE_DIR, name);
3712
- const dest = join8(rulesDir, name);
3218
+ const src = join6(BASE_RULES_TEMPLATE_DIR, name);
3219
+ const dest = join6(rulesDir, name);
3713
3220
  copyFileSync2(src, dest);
3714
3221
  }
3715
3222
  return names;
@@ -3718,8 +3225,8 @@ function syncBaseSkills(skillsDir) {
3718
3225
  mkdirSync6(skillsDir, { recursive: true });
3719
3226
  const names = listBaseSkillDirNames();
3720
3227
  for (const name of names) {
3721
- const src = join8(BASE_SKILLS_TEMPLATE_DIR, name);
3722
- const dest = join8(skillsDir, name);
3228
+ const src = join6(BASE_SKILLS_TEMPLATE_DIR, name);
3229
+ const dest = join6(skillsDir, name);
3723
3230
  cpSync(src, dest, { recursive: true, force: true });
3724
3231
  }
3725
3232
  return names;
@@ -3736,19 +3243,19 @@ function syncSupplementarySkills(skillsDir, list) {
3736
3243
  skipped.push(dirName);
3737
3244
  continue;
3738
3245
  }
3739
- const skillDir = join8(skillsDir, dirName);
3246
+ const skillDir = join6(skillsDir, dirName);
3740
3247
  mkdirSync6(skillDir, { recursive: true });
3741
- writeFileSync9(join8(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
3248
+ writeFileSync6(join6(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
3742
3249
  written.push(dirName);
3743
3250
  }
3744
3251
  const removed = [];
3745
- if (!existsSync7(skillsDir)) return { written, skipped, removed };
3746
- for (const entry of readdirSync4(skillsDir)) {
3747
- const full = join8(skillsDir, entry);
3252
+ if (!existsSync6(skillsDir)) return { written, skipped, removed };
3253
+ for (const entry of readdirSync3(skillsDir)) {
3254
+ const full = join6(skillsDir, entry);
3748
3255
  if (!statSync3(full).isDirectory()) continue;
3749
3256
  if (baseNames.has(entry)) continue;
3750
3257
  if (apiDirNames.has(entry)) continue;
3751
- rmSync3(full, { recursive: true, force: true });
3258
+ rmSync2(full, { recursive: true, force: true });
3752
3259
  removed.push(entry);
3753
3260
  }
3754
3261
  return { written, skipped, removed };
@@ -3758,7 +3265,7 @@ function syncSupplementarySkills(skillsDir, list) {
3758
3265
  async function syncWorkspaceSkills(cfg, workdir) {
3759
3266
  const apmDir = workspaceApmDir(workdir);
3760
3267
  const fsApmDir = toFsPath(apmDir);
3761
- if (!existsSync8(fsApmDir)) {
3268
+ if (!existsSync7(fsApmDir)) {
3762
3269
  throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
3763
3270
  }
3764
3271
  const apmStat = statSync4(fsApmDir);
@@ -3770,12 +3277,12 @@ async function syncWorkspaceSkills(cfg, workdir) {
3770
3277
  if (syncAgentsGuide(apmDir)) {
3771
3278
  console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
3772
3279
  }
3773
- const rulesDir = join9(apmDir, "rules");
3280
+ const rulesDir = join7(apmDir, "rules");
3774
3281
  const ruleNames = syncBaseRules(rulesDir);
3775
3282
  for (const name of ruleNames) {
3776
3283
  console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
3777
3284
  }
3778
- const skillsDir = join9(apmDir, "skills");
3285
+ const skillsDir = join7(apmDir, "skills");
3779
3286
  mkdirSync7(toFsPath(skillsDir), { recursive: true });
3780
3287
  const baseNames = syncBaseSkills(skillsDir);
3781
3288
  for (const name of baseNames) {
@@ -3802,9 +3309,9 @@ async function syncWorkspaceSkills(cfg, workdir) {
3802
3309
  }
3803
3310
 
3804
3311
  // src/commands/sync-session-attachments.ts
3805
- import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
3806
- import { join as join10 } from "path";
3807
- var MANIFEST_FILE2 = ".sync-manifest.json";
3312
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
3313
+ import { join as join8 } from "path";
3314
+ var MANIFEST_FILE = ".sync-manifest.json";
3808
3315
  async function downloadAttachment(cfg, attachmentId) {
3809
3316
  const base = cfg.baseUrl.trim().replace(/\/+$/, "");
3810
3317
  const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
@@ -3819,13 +3326,13 @@ async function downloadAttachment(cfg, attachmentId) {
3819
3326
  return Buffer.from(await res.arrayBuffer());
3820
3327
  }
3821
3328
  function loadManifest(dir) {
3822
- const path = join10(dir, MANIFEST_FILE2);
3823
- if (!existsSync9(path)) {
3329
+ const path = join8(dir, MANIFEST_FILE);
3330
+ if (!existsSync8(path)) {
3824
3331
  return { version: 1, attachments: {} };
3825
3332
  }
3826
3333
  try {
3827
3334
  const parsed = JSON.parse(
3828
- readFileSync9(path, "utf8")
3335
+ readFileSync7(path, "utf8")
3829
3336
  );
3830
3337
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
3831
3338
  return parsed;
@@ -3835,15 +3342,15 @@ function loadManifest(dir) {
3835
3342
  return { version: 1, attachments: {} };
3836
3343
  }
3837
3344
  function saveManifest(dir, manifest) {
3838
- writeFileSync10(
3839
- join10(dir, MANIFEST_FILE2),
3345
+ writeFileSync7(
3346
+ join8(dir, MANIFEST_FILE),
3840
3347
  `${JSON.stringify(manifest, null, 2)}
3841
3348
  `,
3842
3349
  "utf8"
3843
3350
  );
3844
3351
  }
3845
3352
  function isAttachmentUpToDate(entry, item, dest) {
3846
- if (!entry || !existsSync9(dest)) return false;
3353
+ if (!entry || !existsSync8(dest)) return false;
3847
3354
  if (entry.name !== item.name) return false;
3848
3355
  const createdAt = item.createdAt ?? "";
3849
3356
  return entry.createdAt === createdAt;
@@ -3858,7 +3365,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
3858
3365
  const nextManifest = { version: 1, attachments: {} };
3859
3366
  const names = [];
3860
3367
  for (const item of attachments) {
3861
- const dest = join10(dir, item.name);
3368
+ const dest = join8(dir, item.name);
3862
3369
  const entry = manifest.attachments[item.id];
3863
3370
  const createdAt = item.createdAt ?? "";
3864
3371
  names.push(item.name);
@@ -3868,7 +3375,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
3868
3375
  continue;
3869
3376
  }
3870
3377
  const buffer = await downloadAttachment(cfg, item.id);
3871
- writeFileSync10(dest, buffer);
3378
+ writeFileSync7(dest, buffer);
3872
3379
  nextManifest.attachments[item.id] = {
3873
3380
  name: item.name,
3874
3381
  createdAt
@@ -3888,12 +3395,279 @@ async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
3888
3395
  );
3889
3396
  }
3890
3397
 
3398
+ // src/project-documents-sync.ts
3399
+ import {
3400
+ existsSync as existsSync9,
3401
+ readdirSync as readdirSync4,
3402
+ readFileSync as readFileSync8,
3403
+ rmSync as rmSync3,
3404
+ writeFileSync as writeFileSync8
3405
+ } from "fs";
3406
+ import { createHash } from "crypto";
3407
+ import { dirname as dirname6, join as join9, relative as relative2, sep } from "path";
3408
+ var MANIFEST_FILE2 = "manifest.json";
3409
+ function normalizeProjectIdForPath(projectId) {
3410
+ const id = projectId.trim();
3411
+ if (!id) {
3412
+ throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
3413
+ }
3414
+ if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
3415
+ throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
3416
+ }
3417
+ return id;
3418
+ }
3419
+ function projectDocumentsDir(apmRoot, projectId) {
3420
+ const id = normalizeProjectIdForPath(projectId);
3421
+ return join9(apmRoot ?? workspaceApmDir(), "project", id);
3422
+ }
3423
+ function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
3424
+ const normalized = normalizeLocalDocumentPath(documentPath);
3425
+ return join9(
3426
+ projectDocumentsDir(apmRoot, projectId),
3427
+ ...normalized.split("/")
3428
+ );
3429
+ }
3430
+ function normalizeLocalDocumentPath(path) {
3431
+ const trimmed = path.trim().replace(/\\/g, "/");
3432
+ if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
3433
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path}`);
3434
+ }
3435
+ const segments = trimmed.split("/").filter(Boolean);
3436
+ if (segments.some((segment) => segment === ".." || segment === ".")) {
3437
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path}`);
3438
+ }
3439
+ return segments.join("/");
3440
+ }
3441
+ function hashLocalFileContent(content) {
3442
+ return createHash("sha256").update(content, "utf8").digest("hex");
3443
+ }
3444
+ function readLocalManifest(apmRoot, projectId) {
3445
+ const manifestPath3 = join9(
3446
+ projectDocumentsDir(apmRoot, projectId),
3447
+ MANIFEST_FILE2
3448
+ );
3449
+ if (!existsSync9(manifestPath3)) {
3450
+ return null;
3451
+ }
3452
+ try {
3453
+ return JSON.parse(
3454
+ readFileSync8(manifestPath3, "utf8")
3455
+ );
3456
+ } catch {
3457
+ return null;
3458
+ }
3459
+ }
3460
+ function listLocalDocumentPaths(apmRoot, projectId) {
3461
+ const root = projectDocumentsDir(apmRoot, projectId);
3462
+ if (!existsSync9(root)) {
3463
+ return [];
3464
+ }
3465
+ const paths = [];
3466
+ const walk = (dir) => {
3467
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
3468
+ const abs = join9(dir, entry.name);
3469
+ if (entry.isDirectory()) {
3470
+ walk(abs);
3471
+ continue;
3472
+ }
3473
+ if (entry.isFile() && entry.name === MANIFEST_FILE2) {
3474
+ continue;
3475
+ }
3476
+ const rel = relative2(root, abs).split(sep).join("/");
3477
+ paths.push(rel);
3478
+ }
3479
+ };
3480
+ walk(root);
3481
+ return paths.sort();
3482
+ }
3483
+ function diffManifestPaths(remote, local) {
3484
+ const remoteMap = new Map(
3485
+ (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3486
+ );
3487
+ const localMap = new Map(
3488
+ (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3489
+ );
3490
+ const download = [];
3491
+ for (const [path, hash] of remoteMap) {
3492
+ if (localMap.get(path) !== hash) {
3493
+ download.push(path);
3494
+ }
3495
+ }
3496
+ const deleteLocal = [];
3497
+ for (const path of localMap.keys()) {
3498
+ if (!remoteMap.has(path)) {
3499
+ deleteLocal.push(path);
3500
+ }
3501
+ }
3502
+ return { download, deleteLocal };
3503
+ }
3504
+ async function resolveProjectIdForSync(api, workdirPath, options) {
3505
+ const explicit = options?.projectId?.trim();
3506
+ if (explicit) {
3507
+ return { projectId: explicit, diagnostic: null };
3508
+ }
3509
+ try {
3510
+ const baseline = await api.cli.workspaceBaseline({ workdirPath });
3511
+ const projectId = baseline.projectId?.trim() || null;
3512
+ if (projectId) {
3513
+ return { projectId, diagnostic: null };
3514
+ }
3515
+ const projectIds = baseline.projectIds ?? [];
3516
+ if (projectIds.length === 0) {
3517
+ return {
3518
+ projectId: null,
3519
+ diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
3520
+ \u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
3521
+ };
3522
+ }
3523
+ return {
3524
+ projectId: null,
3525
+ diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
3526
+ \u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
3527
+ };
3528
+ } catch (err) {
3529
+ const detail = err instanceof Error ? err.message : String(err);
3530
+ return {
3531
+ projectId: null,
3532
+ diagnostic: detail.replace(/^\[apm\]\s*/, "")
3533
+ };
3534
+ }
3535
+ }
3536
+ async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
3537
+ const empty = {
3538
+ synced: false,
3539
+ projectId: null,
3540
+ downloaded: 0,
3541
+ deleted: 0
3542
+ };
3543
+ const cfg = await tryReadApmConfig();
3544
+ if (!cfg || !resolveApiKey(cfg)) {
3545
+ console.log(
3546
+ "[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
3547
+ );
3548
+ return empty;
3549
+ }
3550
+ const api = createApmApiClient(cfg);
3551
+ const { projectId, diagnostic } = await resolveProjectIdForSync(
3552
+ api,
3553
+ workdirPath,
3554
+ options
3555
+ );
3556
+ if (!projectId) {
3557
+ console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
3558
+ ${diagnostic ?? ""}`);
3559
+ return empty;
3560
+ }
3561
+ const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
3562
+ const docsDir = projectDocumentsDir(targetApmDir, projectId);
3563
+ await ensureDirExists(docsDir);
3564
+ const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
3565
+ { projectId }
3566
+ );
3567
+ if (!remoteManifest) {
3568
+ console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
3569
+ return { ...empty, projectId };
3570
+ }
3571
+ const localManifest = readLocalManifest(targetApmDir, projectId);
3572
+ const { download, deleteLocal } = diffManifestPaths(
3573
+ remoteManifest,
3574
+ localManifest
3575
+ );
3576
+ let downloaded = 0;
3577
+ if (download.length > 0) {
3578
+ const { list } = await api.cli.listProjectDocuments({
3579
+ projectId,
3580
+ paths: download.join(",")
3581
+ });
3582
+ for (const doc of list) {
3583
+ const absPath = toFsPath(
3584
+ projectDocumentLocalPath(targetApmDir, projectId, doc.path)
3585
+ );
3586
+ await ensureDirExists(dirname6(absPath));
3587
+ writeFileSync8(absPath, doc.content, "utf8");
3588
+ downloaded += 1;
3589
+ }
3590
+ }
3591
+ let deleted = 0;
3592
+ for (const path of deleteLocal) {
3593
+ const absPath = toFsPath(
3594
+ projectDocumentLocalPath(targetApmDir, projectId, path)
3595
+ );
3596
+ if (existsSync9(absPath)) {
3597
+ rmSync3(absPath, { force: true });
3598
+ deleted += 1;
3599
+ }
3600
+ }
3601
+ writeFileSync8(
3602
+ toFsPath(join9(docsDir, MANIFEST_FILE2)),
3603
+ `${JSON.stringify(remoteManifest, null, 2)}
3604
+ `,
3605
+ "utf8"
3606
+ );
3607
+ console.log(
3608
+ `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
3609
+ );
3610
+ return {
3611
+ synced: true,
3612
+ projectId,
3613
+ downloaded,
3614
+ deleted
3615
+ };
3616
+ }
3617
+ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
3618
+ const api = createApmApiClient(cfg);
3619
+ const { projectId } = await resolveProjectIdForSync(
3620
+ api,
3621
+ workdirPath,
3622
+ options
3623
+ );
3624
+ if (!projectId) {
3625
+ return 0;
3626
+ }
3627
+ const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
3628
+ const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
3629
+ if (localPaths.length === 0) {
3630
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3631
+ return 0;
3632
+ }
3633
+ const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
3634
+ const remoteHashByPath = new Map(
3635
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3636
+ );
3637
+ const remoteDescriptionByPath = new Map(
3638
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
3639
+ );
3640
+ let synced = 0;
3641
+ for (const path of localPaths) {
3642
+ const absPath = toFsPath(
3643
+ projectDocumentLocalPath(targetApmDir, projectId, path)
3644
+ );
3645
+ const content = readFileSync8(absPath, "utf8");
3646
+ const contentHash = hashLocalFileContent(content);
3647
+ if (remoteHashByPath.get(path) === contentHash) {
3648
+ continue;
3649
+ }
3650
+ await api.cli.upsertProjectDocument({
3651
+ projectId,
3652
+ path,
3653
+ content,
3654
+ description: remoteDescriptionByPath.get(path) ?? void 0
3655
+ });
3656
+ synced += 1;
3657
+ console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path}`);
3658
+ }
3659
+ if (synced === 0) {
3660
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3661
+ }
3662
+ return synced;
3663
+ }
3664
+
3891
3665
  // src/commands/connect/cli-version-sync.ts
3892
- import { existsSync as existsSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync11 } from "fs";
3893
- import { join as join11 } from "path";
3666
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
3667
+ import { join as join10 } from "path";
3894
3668
  var CLI_VERSION_FILE = ".cli-version.json";
3895
3669
  function manifestPath2(apmDir) {
3896
- return join11(apmDir, CLI_VERSION_FILE);
3670
+ return join10(apmDir, CLI_VERSION_FILE);
3897
3671
  }
3898
3672
  function loadManifest2(apmDir) {
3899
3673
  const path = toFsPath(manifestPath2(apmDir));
@@ -3902,7 +3676,7 @@ function loadManifest2(apmDir) {
3902
3676
  }
3903
3677
  try {
3904
3678
  const parsed = JSON.parse(
3905
- readFileSync10(path, "utf8")
3679
+ readFileSync9(path, "utf8")
3906
3680
  );
3907
3681
  if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
3908
3682
  return parsed;
@@ -3913,7 +3687,7 @@ function loadManifest2(apmDir) {
3913
3687
  }
3914
3688
  function saveManifest2(apmDir, cliVersion) {
3915
3689
  const manifest = { version: 1, cliVersion };
3916
- writeFileSync11(
3690
+ writeFileSync9(
3917
3691
  toFsPath(manifestPath2(apmDir)),
3918
3692
  `${JSON.stringify(manifest, null, 2)}
3919
3693
  `,
@@ -4020,9 +3794,6 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
4020
3794
  "\u521D\u59CB\u5316\u5DE5\u4F5C\u533A",
4021
3795
  async () => {
4022
3796
  const { didInit } = await ensureWorkspaceInitialized(workdir);
4023
- if (!didInit) {
4024
- assertApmGitignoredInRepo(workdir);
4025
- }
4026
3797
  return didInit;
4027
3798
  },
4028
3799
  (didInit) => didInit ? "\u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A" : "\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA"