ai-project-manage-cli 7.1.37 → 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.
@@ -601,57 +601,6 @@ async function pushCurrentBranch(cwd) {
601
601
  return true;
602
602
  }
603
603
 
604
- // src/baseline-resolve.ts
605
- function formatBaselineDiagnostic(workdirPath, baselineWorkdirPath, diagnostic) {
606
- 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
607
- \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
608
- }
609
- async function matchRepositoryByGitRemote(api, workdirPath) {
610
- const gitRoot = await resolveGitRepoRoot(workdirPath);
611
- const gitUrl = await tryReadGitOriginUrl(gitRoot);
612
- if (!gitUrl) {
613
- return null;
614
- }
615
- const matched = await api.cli.matchRepository({ url: gitUrl });
616
- const repositoryId = matched.repositoryId?.trim();
617
- const defaultBranch = matched.defaultBranch?.trim();
618
- if (!repositoryId || !defaultBranch) {
619
- return null;
620
- }
621
- console.log(
622
- `[apm] \u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u672A\u5339\u914D\uFF0C\u5DF2\u901A\u8FC7 git remote \u5173\u8054\u4ED3\u5E93: ${gitUrl}`
623
- );
624
- return { repositoryId, defaultBranch };
625
- }
626
- async function resolveWorkspaceBaseline(api, workdirPath) {
627
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
628
- const repositoryId = baseline.repositoryId?.trim();
629
- const defaultBranch = baseline.defaultBranch?.trim();
630
- if (repositoryId && defaultBranch) {
631
- return {
632
- repositoryId,
633
- defaultBranch,
634
- workdirPath: baseline.workdirPath,
635
- matchedViaGitRemote: false
636
- };
637
- }
638
- const viaGit = await matchRepositoryByGitRemote(api, workdirPath);
639
- if (viaGit) {
640
- return {
641
- ...viaGit,
642
- workdirPath: baseline.workdirPath,
643
- matchedViaGitRemote: true
644
- };
645
- }
646
- throw new Error(
647
- `[apm] ${formatBaselineDiagnostic(
648
- workdirPath,
649
- baseline.workdirPath,
650
- baseline.diagnostic
651
- )}`
652
- );
653
- }
654
-
655
604
  // src/command-utils.ts
656
605
  import { copyFileSync, existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
657
606
  import { basename, dirname, extname, join as join2, resolve as resolve2 } from "path";
@@ -2923,493 +2872,142 @@ function resolveMessageReplyFallback(fallback) {
2923
2872
  }
2924
2873
 
2925
2874
  // src/commands/init.ts
2926
- import { join as join6 } from "path";
2927
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
2928
-
2929
- // src/deployment-config-sync.ts
2930
2875
  import { join as join4 } from "path";
2931
- import { writeFileSync as writeFileSync5 } from "fs";
2932
- var TEMPLATE_HINT = "\u4FDD\u7559\u6A21\u677F .apm/apm.config.json";
2933
- 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";
2934
- async function resolveRepositoryIdForSync(api, workdirPath) {
2935
- try {
2936
- const baseline = await resolveWorkspaceBaseline(api, workdirPath);
2937
- return { repositoryId: baseline.repositoryId, diagnostic: null };
2938
- } catch (err) {
2939
- const detail = err instanceof Error ? err.message : String(err);
2940
- return {
2941
- repositoryId: null,
2942
- diagnostic: detail.replace(/^\[apm\]\s*/, "")
2943
- };
2944
- }
2945
- }
2946
- async function writeDeploymentConfigContent(apmDir, content, configName) {
2947
- let parsed;
2948
- try {
2949
- parsed = JSON.parse(content);
2950
- } catch {
2951
- console.warn(
2952
- `[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"
2953
2892
  );
2954
- return false;
2955
2893
  }
2956
- const apmConfigPath = toFsPath(join4(apmDir, "apm.config.json"));
2957
- writeFileSync5(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
2958
- `, "utf8");
2959
- console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${configName}`);
2960
- console.log("[apm] \u5DF2\u5199\u5165 .apm/apm.config.json");
2961
- return true;
2894
+ console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
2895
+ return { didInit: true };
2962
2896
  }
2963
- async function syncRemoteDeploymentConfig(workdirPath, apmDir, options) {
2964
- const cfg = await tryReadApmConfig();
2965
- if (!cfg || !resolveApiKey(cfg)) {
2966
- console.log(
2967
- `[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
2968
- [apm] \u8BF7\u5148\u6267\u884C apm login\uFF0C\u518D\u6267\u884C apm sync-deploy-config \u62C9\u53D6\u6700\u65B0\u914D\u7F6E\u3002`
2969
- );
2970
- return { synced: false, repositoryId: null };
2971
- }
2972
- const api = createApmApiClient(cfg);
2973
- const preferredId = options?.repositoryId?.trim() || "";
2974
- let repositoryId = preferredId || null;
2975
- let diagnostic = null;
2976
- if (!repositoryId) {
2977
- const resolved = await resolveRepositoryIdForSync(api, workdirPath);
2978
- repositoryId = resolved.repositoryId;
2979
- diagnostic = resolved.diagnostic;
2980
- }
2981
- if (!repositoryId) {
2982
- console.log(
2983
- `[apm] \u672A\u80FD\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E\uFF08${TEMPLATE_HINT}\uFF09\u3002
2984
- ${diagnostic ?? ""}
2985
- [apm] ${SYNC_HINT}`
2986
- );
2987
- return { synced: false, repositoryId: null };
2988
- }
2989
- const { config } = await api.cli.getDeploymentConfiguration({ repositoryId });
2990
- if (!config) {
2991
- console.log(
2992
- `[apm] \u672A\u627E\u5230\u5173\u8054\u4ED3\u5E93\u7684\u90E8\u7F72\u914D\u7F6E\uFF08${TEMPLATE_HINT}\uFF0CrepositoryId\uFF1A${repositoryId}\uFF09\u3002
2993
- [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`
2994
- );
2995
- return { synced: false, repositoryId };
2897
+ async function ensureWorkspaceInitialized(workdir, options) {
2898
+ if (isWorkspaceApmInitialized(workdir)) {
2899
+ return { didInit: false };
2996
2900
  }
2997
- const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
2998
- const wrote = await writeDeploymentConfigContent(
2999
- targetApmDir,
3000
- config.content,
3001
- 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`
3002
2903
  );
3003
- if (!wrote) {
3004
- return { synced: false, repositoryId };
3005
- }
3006
- return { synced: true, repositoryId, configName: config.name };
2904
+ return initializeWorkspace(workdir, options);
3007
2905
  }
3008
2906
 
3009
- // src/project-documents-sync.ts
3010
- import {
3011
- existsSync as existsSync6,
3012
- readdirSync as readdirSync3,
3013
- readFileSync as readFileSync5,
3014
- rmSync as rmSync2,
3015
- writeFileSync as writeFileSync6
3016
- } from "fs";
3017
- import { createHash } from "crypto";
3018
- import { dirname as dirname5, join as join5, relative as relative2, sep } from "path";
3019
- var MANIFEST_FILE = "manifest.json";
3020
- function normalizeProjectIdForPath(projectId) {
3021
- const id = projectId.trim();
3022
- if (!id) {
3023
- throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
3024
- }
3025
- if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
3026
- throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
3027
- }
3028
- return id;
2907
+ // src/commands/connect/pre-step-cache.ts
2908
+ function sessionWorkdirKey(sessionId, workdir) {
2909
+ return `${sessionId}\0${workdir}`;
3029
2910
  }
3030
- function projectDocumentsDir(apmRoot, projectId) {
3031
- const id = normalizeProjectIdForPath(projectId);
3032
- return join5(apmRoot ?? workspaceApmDir(), "project", id);
2911
+ var lastBranchKey = null;
2912
+ function shouldRunBranch(sessionId, workdir) {
2913
+ return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
3033
2914
  }
3034
- function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
3035
- const normalized = normalizeLocalDocumentPath(documentPath);
3036
- return join5(
3037
- projectDocumentsDir(apmRoot, projectId),
3038
- ...normalized.split("/")
3039
- );
2915
+ function markBranchDone(sessionId, workdir) {
2916
+ lastBranchKey = sessionWorkdirKey(sessionId, workdir);
3040
2917
  }
3041
- function normalizeLocalDocumentPath(path) {
3042
- const trimmed = path.trim().replace(/\\/g, "/");
3043
- if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
3044
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path}`);
3045
- }
3046
- const segments = trimmed.split("/").filter(Boolean);
3047
- if (segments.some((segment) => segment === ".." || segment === ".")) {
3048
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path}`);
3049
- }
3050
- 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);
3051
2929
  }
3052
- function hashLocalFileContent(content) {
3053
- return createHash("sha256").update(content, "utf8").digest("hex");
2930
+ function shouldEnsureWebIdePullRequestsAfterAgent(action) {
2931
+ return POST_AGENT_PR_ACTIONS.has(action);
3054
2932
  }
3055
- function readLocalManifest(apmRoot, projectId) {
3056
- const manifestPath3 = join5(
3057
- projectDocumentsDir(apmRoot, projectId),
3058
- 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`
3059
2938
  );
3060
- if (!existsSync6(manifestPath3)) {
3061
- return null;
3062
- }
3063
- try {
3064
- return JSON.parse(
3065
- readFileSync5(manifestPath3, "utf8")
3066
- );
3067
- } catch {
3068
- return null;
3069
- }
3070
- }
3071
- function listLocalDocumentPaths(apmRoot, projectId) {
3072
- const root = projectDocumentsDir(apmRoot, projectId);
3073
- if (!existsSync6(root)) {
3074
- return [];
2939
+ if (result.items.length === 0) {
2940
+ lines.push("\u672A\u5904\u7406\u4EFB\u4F55\u4ED3\u5E93\u3002");
2941
+ return lines.join("\n");
3075
2942
  }
3076
- const paths = [];
3077
- const walk = (dir) => {
3078
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
3079
- const abs = join5(dir, entry.name);
3080
- if (entry.isDirectory()) {
3081
- walk(abs);
3082
- continue;
3083
- }
3084
- if (entry.isFile() && entry.name === MANIFEST_FILE) {
3085
- continue;
3086
- }
3087
- const rel = relative2(root, abs).split(sep).join("/");
3088
- 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;
3089
2954
  }
3090
- };
3091
- walk(root);
3092
- return paths.sort();
3093
- }
3094
- function diffManifestPaths(remote, local) {
3095
- const remoteMap = new Map(
3096
- (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3097
- );
3098
- const localMap = new Map(
3099
- (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3100
- );
3101
- const download = [];
3102
- for (const [path, hash] of remoteMap) {
3103
- if (localMap.get(path) !== hash) {
3104
- 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;
3105
2960
  }
3106
- }
3107
- const deleteLocal = [];
3108
- for (const path of localMap.keys()) {
3109
- if (!remoteMap.has(path)) {
3110
- deleteLocal.push(path);
2961
+ if (item.outcome === "skipped") {
2962
+ lines.push(`- **${item.label}**\uFF1A\u5DF2\u8DF3\u8FC7 \u2014 ${item.detail}`);
2963
+ continue;
3111
2964
  }
2965
+ lines.push(`- **${item.label}**\uFF1A\u521B\u5EFA\u5931\u8D25 \u2014 ${item.detail}`);
3112
2966
  }
3113
- return { download, deleteLocal };
2967
+ return lines.join("\n");
3114
2968
  }
3115
- async function resolveProjectIdForSync(api, workdirPath, options) {
3116
- const explicit = options?.projectId?.trim();
3117
- if (explicit) {
3118
- return { projectId: explicit, diagnostic: null };
3119
- }
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;
3120
2980
  try {
3121
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
3122
- const projectId = baseline.projectId?.trim() || null;
3123
- if (projectId) {
3124
- return { projectId, diagnostic: null };
3125
- }
3126
- const projectIds = baseline.projectIds ?? [];
3127
- if (projectIds.length === 0) {
3128
- return {
3129
- projectId: null,
3130
- 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
3131
- \u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
3132
- };
3133
- }
2981
+ const manifest = loadWorkspaceReposCache(workdir);
2982
+ repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
2983
+ } catch (err) {
2984
+ const tip = `\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25: ${err instanceof Error ? err.message : err}`;
2985
+ console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3134
2986
  return {
3135
- projectId: null,
3136
- diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
3137
- \u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
2987
+ ...empty(),
2988
+ failed: 1,
2989
+ tips: [tip],
2990
+ items: [
2991
+ {
2992
+ label: "\u5DE5\u4F5C\u533A",
2993
+ outcome: "failed",
2994
+ detail: tip
2995
+ }
2996
+ ]
3138
2997
  };
2998
+ }
2999
+ let projectBaseBranch = "";
3000
+ try {
3001
+ const baseline = await api.cli.projectBaseBranch({ taskId });
3002
+ projectBaseBranch = baseline.baseBranch?.trim() ?? "";
3139
3003
  } catch (err) {
3140
- const detail = err instanceof Error ? err.message : String(err);
3004
+ const tip = `\u8BFB\u53D6\u9879\u76EE\u57FA\u7EBF\u5206\u652F\u5931\u8D25: ${err instanceof Error ? err.message : err}`;
3005
+ console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3141
3006
  return {
3142
- projectId: null,
3143
- diagnostic: detail.replace(/^\[apm\]\s*/, "")
3144
- };
3145
- }
3146
- }
3147
- async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
3148
- const empty = {
3149
- synced: false,
3150
- projectId: null,
3151
- downloaded: 0,
3152
- deleted: 0
3153
- };
3154
- const cfg = await tryReadApmConfig();
3155
- if (!cfg || !resolveApiKey(cfg)) {
3156
- console.log(
3157
- "[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"
3158
- );
3159
- return empty;
3160
- }
3161
- const api = createApmApiClient(cfg);
3162
- const { projectId, diagnostic } = await resolveProjectIdForSync(
3163
- api,
3164
- workdirPath,
3165
- options
3166
- );
3167
- if (!projectId) {
3168
- console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
3169
- ${diagnostic ?? ""}`);
3170
- return empty;
3171
- }
3172
- const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
3173
- const docsDir = projectDocumentsDir(targetApmDir, projectId);
3174
- await ensureDirExists(docsDir);
3175
- const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
3176
- { projectId }
3177
- );
3178
- if (!remoteManifest) {
3179
- console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
3180
- return { ...empty, projectId };
3181
- }
3182
- const localManifest = readLocalManifest(targetApmDir, projectId);
3183
- const { download, deleteLocal } = diffManifestPaths(
3184
- remoteManifest,
3185
- localManifest
3186
- );
3187
- let downloaded = 0;
3188
- if (download.length > 0) {
3189
- const { list } = await api.cli.listProjectDocuments({
3190
- projectId,
3191
- paths: download.join(",")
3192
- });
3193
- for (const doc of list) {
3194
- const absPath = toFsPath(
3195
- projectDocumentLocalPath(targetApmDir, projectId, doc.path)
3196
- );
3197
- await ensureDirExists(dirname5(absPath));
3198
- writeFileSync6(absPath, doc.content, "utf8");
3199
- downloaded += 1;
3200
- }
3201
- }
3202
- let deleted = 0;
3203
- for (const path of deleteLocal) {
3204
- const absPath = toFsPath(
3205
- projectDocumentLocalPath(targetApmDir, projectId, path)
3206
- );
3207
- if (existsSync6(absPath)) {
3208
- rmSync2(absPath, { force: true });
3209
- deleted += 1;
3210
- }
3211
- }
3212
- writeFileSync6(
3213
- toFsPath(join5(docsDir, MANIFEST_FILE)),
3214
- `${JSON.stringify(remoteManifest, null, 2)}
3215
- `,
3216
- "utf8"
3217
- );
3218
- console.log(
3219
- `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
3220
- );
3221
- return {
3222
- synced: true,
3223
- projectId,
3224
- downloaded,
3225
- deleted
3226
- };
3227
- }
3228
- async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
3229
- const api = createApmApiClient(cfg);
3230
- const { projectId } = await resolveProjectIdForSync(
3231
- api,
3232
- workdirPath,
3233
- options
3234
- );
3235
- if (!projectId) {
3236
- return 0;
3237
- }
3238
- const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
3239
- const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
3240
- if (localPaths.length === 0) {
3241
- console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3242
- return 0;
3243
- }
3244
- const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
3245
- const remoteHashByPath = new Map(
3246
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3247
- );
3248
- const remoteDescriptionByPath = new Map(
3249
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
3250
- );
3251
- let synced = 0;
3252
- for (const path of localPaths) {
3253
- const absPath = toFsPath(
3254
- projectDocumentLocalPath(targetApmDir, projectId, path)
3255
- );
3256
- const content = readFileSync5(absPath, "utf8");
3257
- const contentHash = hashLocalFileContent(content);
3258
- if (remoteHashByPath.get(path) === contentHash) {
3259
- continue;
3260
- }
3261
- await api.cli.upsertProjectDocument({
3262
- projectId,
3263
- path,
3264
- content,
3265
- description: remoteDescriptionByPath.get(path) ?? void 0
3266
- });
3267
- synced += 1;
3268
- console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path}`);
3269
- }
3270
- if (synced === 0) {
3271
- console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3272
- }
3273
- return synced;
3274
- }
3275
-
3276
- // src/commands/init.ts
3277
- async function initializeWorkspace(workdir, options) {
3278
- await ensureWorkspaceApmDirForInit(workdir);
3279
- const apmDir = workspaceApmDir(workdir);
3280
- await copyTemplateFiles(apmDir, workdir);
3281
- const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
3282
- await syncProjectDocumentsPull(workdir, apmDir);
3283
- const trimmedName = options?.name?.trim();
3284
- if (trimmedName) {
3285
- const apmConfigPath = toFsPath(join6(apmDir, "apm.config.json"));
3286
- const config = readFileSync6(apmConfigPath, "utf8");
3287
- const configJson = JSON.parse(config);
3288
- configJson.name = trimmedName;
3289
- writeFileSync7(
3290
- apmConfigPath,
3291
- `${JSON.stringify(configJson, null, 2)}
3292
- `,
3293
- "utf8"
3294
- );
3295
- }
3296
- console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
3297
- return { didInit: true, syncResult };
3298
- }
3299
- async function ensureWorkspaceInitialized(workdir, options) {
3300
- if (isWorkspaceApmInitialized(workdir)) {
3301
- return { didInit: false };
3302
- }
3303
- console.log(
3304
- `[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`
3305
- );
3306
- return initializeWorkspace(workdir, options);
3307
- }
3308
-
3309
- // src/commands/connect/pre-step-cache.ts
3310
- function sessionWorkdirKey(sessionId, workdir) {
3311
- return `${sessionId}\0${workdir}`;
3312
- }
3313
- var lastBranchKey = null;
3314
- function shouldRunBranch(sessionId, workdir) {
3315
- return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
3316
- }
3317
- function markBranchDone(sessionId, workdir) {
3318
- lastBranchKey = sessionWorkdirKey(sessionId, workdir);
3319
- }
3320
-
3321
- // src/commands/connect/webide-pull-requests.ts
3322
- var PRE_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set([
3323
- "generate-cases",
3324
- "run-auto-test",
3325
- // 验收合并前兜底确保 PR 存在
3326
- "accept"
3327
- ]);
3328
- var POST_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set(["create-pr", "finish-manual-test"]);
3329
- function shouldEnsureWebIdePullRequests(action) {
3330
- return PRE_AGENT_PR_ACTIONS.has(action);
3331
- }
3332
- function shouldEnsureWebIdePullRequestsAfterAgent(action) {
3333
- return POST_AGENT_PR_ACTIONS.has(action);
3334
- }
3335
- function formatWebIdePullRequestSummary(result) {
3336
- const lines = [];
3337
- lines.push("## PR \u521B\u5EFA\u7ED3\u679C");
3338
- lines.push(
3339
- `\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`
3340
- );
3341
- if (result.items.length === 0) {
3342
- lines.push("\u672A\u5904\u7406\u4EFB\u4F55\u4ED3\u5E93\u3002");
3343
- return lines.join("\n");
3344
- }
3345
- lines.push("");
3346
- for (const item of result.items) {
3347
- if (item.outcome === "created") {
3348
- const num = item.number != null && item.number > 0 ? `#${item.number}` : "\uFF08\u65E0\u7F16\u53F7\uFF09";
3349
- const branch = item.headBranch && item.baseBranch ? `${item.headBranch} \u2192 ${item.baseBranch}` : item.baseBranch ? `\u76EE\u6807\u5206\u652F ${item.baseBranch}` : "";
3350
- const state = item.state ? `\u72B6\u6001 ${item.state}` : "";
3351
- const url = item.url?.trim() ? item.url.trim() : "";
3352
- lines.push(
3353
- `- **${item.label}**\uFF1A\u5DF2\u521B\u5EFA ${num}${branch ? `\uFF0C${branch}` : ""}${state ? `\uFF0C${state}` : ""}${url ? `\uFF0C${url}` : ""}`
3354
- );
3355
- continue;
3356
- }
3357
- if (item.outcome === "skipped_no_changes") {
3358
- lines.push(
3359
- `- **${item.label}**\uFF1A\u76F8\u5BF9\u57FA\u7EBF ${item.baseBranch || "\uFF08\u672A\u77E5\uFF09"} \u65E0\u4EE3\u7801\u53D8\u66F4\uFF0C\u672A\u521B\u5EFA PR`
3360
- );
3361
- continue;
3362
- }
3363
- if (item.outcome === "skipped") {
3364
- lines.push(`- **${item.label}**\uFF1A\u5DF2\u8DF3\u8FC7 \u2014 ${item.detail}`);
3365
- continue;
3366
- }
3367
- lines.push(`- **${item.label}**\uFF1A\u521B\u5EFA\u5931\u8D25 \u2014 ${item.detail}`);
3368
- }
3369
- return lines.join("\n");
3370
- }
3371
- async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3372
- const api = createApmApiClient(cfg);
3373
- const empty = () => ({
3374
- ensured: 0,
3375
- skipped: 0,
3376
- skippedNoChanges: 0,
3377
- failed: 0,
3378
- items: [],
3379
- tips: []
3380
- });
3381
- let repoRoots;
3382
- try {
3383
- const manifest = loadWorkspaceReposCache(workdir);
3384
- repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
3385
- } catch (err) {
3386
- const tip = `\u8BFB\u53D6\u4ED3\u5E93\u6E05\u5355\u5931\u8D25: ${err instanceof Error ? err.message : err}`;
3387
- console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3388
- return {
3389
- ...empty(),
3390
- failed: 1,
3391
- tips: [tip],
3392
- items: [
3393
- {
3394
- label: "\u5DE5\u4F5C\u533A",
3395
- outcome: "failed",
3396
- detail: tip
3397
- }
3398
- ]
3399
- };
3400
- }
3401
- let projectBaseBranch = "";
3402
- try {
3403
- const baseline = await api.cli.projectBaseBranch({ taskId });
3404
- projectBaseBranch = baseline.baseBranch?.trim() ?? "";
3405
- } catch (err) {
3406
- const tip = `\u8BFB\u53D6\u9879\u76EE\u57FA\u7EBF\u5206\u652F\u5931\u8D25: ${err instanceof Error ? err.message : err}`;
3407
- console.warn(`[apm] WebIDE PR\uFF1A${tip}`);
3408
- return {
3409
- ...empty(),
3410
- failed: 1,
3411
- tips: [tip],
3412
- items: [{ label: "\u5DE5\u4F5C\u533A", outcome: "failed", detail: tip }]
3007
+ ...empty(),
3008
+ failed: 1,
3009
+ tips: [tip],
3010
+ items: [{ label: "\u5DE5\u4F5C\u533A", outcome: "failed", detail: tip }]
3413
3011
  };
3414
3012
  }
3415
3013
  if (!projectBaseBranch) {
@@ -3555,14 +3153,14 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3555
3153
  }
3556
3154
 
3557
3155
  // src/version.ts
3558
- import { readFileSync as readFileSync7 } from "fs";
3559
- 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";
3560
3158
  import { fileURLToPath as fileURLToPath2 } from "url";
3561
3159
  function readCliVersion() {
3562
3160
  try {
3563
- const dir = dirname6(fileURLToPath2(import.meta.url));
3564
- const pkgPath = join7(dir, "..", "package.json");
3565
- const pkg = JSON.parse(readFileSync7(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"));
3566
3164
  return pkg.version ?? "0.0.0";
3567
3165
  } catch {
3568
3166
  return "0.0.0";
@@ -3570,46 +3168,46 @@ function readCliVersion() {
3570
3168
  }
3571
3169
 
3572
3170
  // src/commands/update-skills.ts
3573
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, statSync as statSync4 } from "fs";
3574
- 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";
3575
3173
 
3576
3174
  // src/skills-sync.ts
3577
3175
  import {
3578
3176
  copyFileSync as copyFileSync2,
3579
3177
  cpSync,
3580
- existsSync as existsSync7,
3178
+ existsSync as existsSync6,
3581
3179
  mkdirSync as mkdirSync6,
3582
- readdirSync as readdirSync4,
3583
- rmSync as rmSync3,
3180
+ readdirSync as readdirSync3,
3181
+ rmSync as rmSync2,
3584
3182
  statSync as statSync3,
3585
- writeFileSync as writeFileSync8
3183
+ writeFileSync as writeFileSync6
3586
3184
  } from "fs";
3587
- import { join as join8 } from "path";
3588
- var AGENTS_TEMPLATE_PATH = join8(CLI_TEMPLATE_DIR, "AGENTS.md");
3589
- var BASE_SKILLS_TEMPLATE_DIR = join8(CLI_TEMPLATE_DIR, "skills");
3590
- 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");
3591
3189
  function sanitizeSkillDirName(name) {
3592
3190
  const trimmed = name.trim();
3593
3191
  if (!trimmed) return "skill";
3594
3192
  return trimmed.replace(/[/\\:*?"<>|]/g, "_");
3595
3193
  }
3596
3194
  function listBaseSkillDirNames() {
3597
- if (!existsSync7(BASE_SKILLS_TEMPLATE_DIR)) return [];
3598
- return readdirSync4(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
3599
- 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);
3600
3198
  return statSync3(path).isDirectory();
3601
3199
  });
3602
3200
  }
3603
3201
  function syncAgentsGuide(apmDir) {
3604
- if (!existsSync7(AGENTS_TEMPLATE_PATH)) return false;
3202
+ if (!existsSync6(AGENTS_TEMPLATE_PATH)) return false;
3605
3203
  mkdirSync6(apmDir, { recursive: true });
3606
- copyFileSync2(AGENTS_TEMPLATE_PATH, join8(apmDir, "AGENTS.md"));
3204
+ copyFileSync2(AGENTS_TEMPLATE_PATH, join6(apmDir, "AGENTS.md"));
3607
3205
  return true;
3608
3206
  }
3609
3207
  function listBaseRuleFileNames() {
3610
- if (!existsSync7(BASE_RULES_TEMPLATE_DIR)) return [];
3611
- return readdirSync4(BASE_RULES_TEMPLATE_DIR).filter((name) => {
3612
- 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);
3613
3211
  return statSync3(path).isFile();
3614
3212
  });
3615
3213
  }
@@ -3617,8 +3215,8 @@ function syncBaseRules(rulesDir) {
3617
3215
  mkdirSync6(rulesDir, { recursive: true });
3618
3216
  const names = listBaseRuleFileNames();
3619
3217
  for (const name of names) {
3620
- const src = join8(BASE_RULES_TEMPLATE_DIR, name);
3621
- const dest = join8(rulesDir, name);
3218
+ const src = join6(BASE_RULES_TEMPLATE_DIR, name);
3219
+ const dest = join6(rulesDir, name);
3622
3220
  copyFileSync2(src, dest);
3623
3221
  }
3624
3222
  return names;
@@ -3627,8 +3225,8 @@ function syncBaseSkills(skillsDir) {
3627
3225
  mkdirSync6(skillsDir, { recursive: true });
3628
3226
  const names = listBaseSkillDirNames();
3629
3227
  for (const name of names) {
3630
- const src = join8(BASE_SKILLS_TEMPLATE_DIR, name);
3631
- const dest = join8(skillsDir, name);
3228
+ const src = join6(BASE_SKILLS_TEMPLATE_DIR, name);
3229
+ const dest = join6(skillsDir, name);
3632
3230
  cpSync(src, dest, { recursive: true, force: true });
3633
3231
  }
3634
3232
  return names;
@@ -3645,19 +3243,19 @@ function syncSupplementarySkills(skillsDir, list) {
3645
3243
  skipped.push(dirName);
3646
3244
  continue;
3647
3245
  }
3648
- const skillDir = join8(skillsDir, dirName);
3246
+ const skillDir = join6(skillsDir, dirName);
3649
3247
  mkdirSync6(skillDir, { recursive: true });
3650
- writeFileSync8(join8(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
3248
+ writeFileSync6(join6(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
3651
3249
  written.push(dirName);
3652
3250
  }
3653
3251
  const removed = [];
3654
- if (!existsSync7(skillsDir)) return { written, skipped, removed };
3655
- for (const entry of readdirSync4(skillsDir)) {
3656
- 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);
3657
3255
  if (!statSync3(full).isDirectory()) continue;
3658
3256
  if (baseNames.has(entry)) continue;
3659
3257
  if (apiDirNames.has(entry)) continue;
3660
- rmSync3(full, { recursive: true, force: true });
3258
+ rmSync2(full, { recursive: true, force: true });
3661
3259
  removed.push(entry);
3662
3260
  }
3663
3261
  return { written, skipped, removed };
@@ -3667,7 +3265,7 @@ function syncSupplementarySkills(skillsDir, list) {
3667
3265
  async function syncWorkspaceSkills(cfg, workdir) {
3668
3266
  const apmDir = workspaceApmDir(workdir);
3669
3267
  const fsApmDir = toFsPath(apmDir);
3670
- if (!existsSync8(fsApmDir)) {
3268
+ if (!existsSync7(fsApmDir)) {
3671
3269
  throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
3672
3270
  }
3673
3271
  const apmStat = statSync4(fsApmDir);
@@ -3679,12 +3277,12 @@ async function syncWorkspaceSkills(cfg, workdir) {
3679
3277
  if (syncAgentsGuide(apmDir)) {
3680
3278
  console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
3681
3279
  }
3682
- const rulesDir = join9(apmDir, "rules");
3280
+ const rulesDir = join7(apmDir, "rules");
3683
3281
  const ruleNames = syncBaseRules(rulesDir);
3684
3282
  for (const name of ruleNames) {
3685
3283
  console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
3686
3284
  }
3687
- const skillsDir = join9(apmDir, "skills");
3285
+ const skillsDir = join7(apmDir, "skills");
3688
3286
  mkdirSync7(toFsPath(skillsDir), { recursive: true });
3689
3287
  const baseNames = syncBaseSkills(skillsDir);
3690
3288
  for (const name of baseNames) {
@@ -3711,9 +3309,9 @@ async function syncWorkspaceSkills(cfg, workdir) {
3711
3309
  }
3712
3310
 
3713
3311
  // src/commands/sync-session-attachments.ts
3714
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
3715
- import { join as join10 } from "path";
3716
- 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";
3717
3315
  async function downloadAttachment(cfg, attachmentId) {
3718
3316
  const base = cfg.baseUrl.trim().replace(/\/+$/, "");
3719
3317
  const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
@@ -3728,13 +3326,13 @@ async function downloadAttachment(cfg, attachmentId) {
3728
3326
  return Buffer.from(await res.arrayBuffer());
3729
3327
  }
3730
3328
  function loadManifest(dir) {
3731
- const path = join10(dir, MANIFEST_FILE2);
3732
- if (!existsSync9(path)) {
3329
+ const path = join8(dir, MANIFEST_FILE);
3330
+ if (!existsSync8(path)) {
3733
3331
  return { version: 1, attachments: {} };
3734
3332
  }
3735
3333
  try {
3736
3334
  const parsed = JSON.parse(
3737
- readFileSync8(path, "utf8")
3335
+ readFileSync7(path, "utf8")
3738
3336
  );
3739
3337
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
3740
3338
  return parsed;
@@ -3744,15 +3342,15 @@ function loadManifest(dir) {
3744
3342
  return { version: 1, attachments: {} };
3745
3343
  }
3746
3344
  function saveManifest(dir, manifest) {
3747
- writeFileSync9(
3748
- join10(dir, MANIFEST_FILE2),
3345
+ writeFileSync7(
3346
+ join8(dir, MANIFEST_FILE),
3749
3347
  `${JSON.stringify(manifest, null, 2)}
3750
3348
  `,
3751
3349
  "utf8"
3752
3350
  );
3753
3351
  }
3754
3352
  function isAttachmentUpToDate(entry, item, dest) {
3755
- if (!entry || !existsSync9(dest)) return false;
3353
+ if (!entry || !existsSync8(dest)) return false;
3756
3354
  if (entry.name !== item.name) return false;
3757
3355
  const createdAt = item.createdAt ?? "";
3758
3356
  return entry.createdAt === createdAt;
@@ -3767,7 +3365,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
3767
3365
  const nextManifest = { version: 1, attachments: {} };
3768
3366
  const names = [];
3769
3367
  for (const item of attachments) {
3770
- const dest = join10(dir, item.name);
3368
+ const dest = join8(dir, item.name);
3771
3369
  const entry = manifest.attachments[item.id];
3772
3370
  const createdAt = item.createdAt ?? "";
3773
3371
  names.push(item.name);
@@ -3777,7 +3375,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
3777
3375
  continue;
3778
3376
  }
3779
3377
  const buffer = await downloadAttachment(cfg, item.id);
3780
- writeFileSync9(dest, buffer);
3378
+ writeFileSync7(dest, buffer);
3781
3379
  nextManifest.attachments[item.id] = {
3782
3380
  name: item.name,
3783
3381
  createdAt
@@ -3797,12 +3395,279 @@ async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
3797
3395
  );
3798
3396
  }
3799
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
+
3800
3665
  // src/commands/connect/cli-version-sync.ts
3801
- import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
3802
- 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";
3803
3668
  var CLI_VERSION_FILE = ".cli-version.json";
3804
3669
  function manifestPath2(apmDir) {
3805
- return join11(apmDir, CLI_VERSION_FILE);
3670
+ return join10(apmDir, CLI_VERSION_FILE);
3806
3671
  }
3807
3672
  function loadManifest2(apmDir) {
3808
3673
  const path = toFsPath(manifestPath2(apmDir));
@@ -3822,7 +3687,7 @@ function loadManifest2(apmDir) {
3822
3687
  }
3823
3688
  function saveManifest2(apmDir, cliVersion) {
3824
3689
  const manifest = { version: 1, cliVersion };
3825
- writeFileSync10(
3690
+ writeFileSync9(
3826
3691
  toFsPath(manifestPath2(apmDir)),
3827
3692
  `${JSON.stringify(manifest, null, 2)}
3828
3693
  `,