ai-project-manage-cli 7.1.37 → 7.1.39

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";
@@ -710,8 +659,12 @@ var WORKSPACE_TEMPLATE_SUBDIRS = [
710
659
  "rules",
711
660
  "deploy"
712
661
  ];
662
+ var WEBIDE_AGENTS_TEMPLATE_NAME = "AGENTS.webide.md";
713
663
  function shouldSkipTemplateEntry(name) {
714
- return name === ".DS_Store" || name === "Thumbs.db";
664
+ return name === ".DS_Store" || name === "Thumbs.db" || name === WEBIDE_AGENTS_TEMPLATE_NAME;
665
+ }
666
+ function isWebIdeRuleFileName(name) {
667
+ return name.startsWith("webide");
715
668
  }
716
669
  function copyTemplateEntry(src, dest) {
717
670
  const fsSrc = toFsPath(src);
@@ -777,6 +730,68 @@ async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
777
730
  }
778
731
  assertTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
779
732
  }
733
+ function listWebIdeRuleFileNames(rulesDir) {
734
+ const fsRulesDir = toFsPath(rulesDir);
735
+ if (!existsSync(fsRulesDir) || !statSync(fsRulesDir).isDirectory()) {
736
+ return [];
737
+ }
738
+ return readdirSync(fsRulesDir).filter((name) => {
739
+ if (shouldSkipTemplateEntry(name) || !isWebIdeRuleFileName(name)) {
740
+ return false;
741
+ }
742
+ return statSync(toFsPath(join2(rulesDir, name))).isFile();
743
+ });
744
+ }
745
+ function assertWebIdeTemplateCopiedToApm(apmDir, workdir) {
746
+ const required = ["AGENTS.md", "apm.config.json", "rules"];
747
+ for (const item of required) {
748
+ const path = join2(apmDir, item);
749
+ if (!existsSync(toFsPath(path))) {
750
+ throw new Error(`[apm] WebIDE \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path}`);
751
+ }
752
+ }
753
+ const rulesDir = join2(apmDir, "rules");
754
+ const ruleNames = listWebIdeRuleFileNames(rulesDir);
755
+ if (ruleNames.length === 0) {
756
+ throw new Error(`[apm] WebIDE \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11 webide \u89C4\u5219: ${rulesDir}`);
757
+ }
758
+ const leakedRules = join2(workdir, "rules");
759
+ const apmRules = join2(apmDir, "rules");
760
+ if (existsSync(toFsPath(leakedRules)) && !existsSync(toFsPath(join2(apmRules, "webide_reply.md")))) {
761
+ throw new Error(
762
+ `[apm] \u6A21\u677F\u88AB\u590D\u5236\u5230\u9519\u8BEF\u4F4D\u7F6E: ${leakedRules}\uFF08\u5E94\u5728 ${apmRules}\uFF09`
763
+ );
764
+ }
765
+ }
766
+ async function copyWebIdeTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
767
+ const resolvedTarget = resolve2(targetDir);
768
+ const templateDir = resolve2(CLI_TEMPLATE_DIR);
769
+ const fsTemplateDir = toFsPath(templateDir);
770
+ if (!existsSync(fsTemplateDir) || !statSync(fsTemplateDir).isDirectory()) {
771
+ throw new Error(`[apm] \u672A\u627E\u5230 CLI \u6A21\u677F\u76EE\u5F55: ${templateDir}`);
772
+ }
773
+ const agentsSrc = join2(templateDir, WEBIDE_AGENTS_TEMPLATE_NAME);
774
+ if (!existsSync(toFsPath(agentsSrc))) {
775
+ throw new Error(`[apm] \u672A\u627E\u5230 WebIDE \u6307\u5357: ${agentsSrc}`);
776
+ }
777
+ const rulesSrc = join2(templateDir, "rules");
778
+ const ruleNames = listWebIdeRuleFileNames(rulesSrc);
779
+ if (ruleNames.length === 0) {
780
+ throw new Error(`[apm] CLI \u6A21\u677F\u4E2D\u6CA1\u6709 webide \u89C4\u5219: ${rulesSrc}`);
781
+ }
782
+ mkdirSync2(toFsPath(resolvedTarget), { recursive: true });
783
+ copyFileSync(toFsPath(agentsSrc), toFsPath(join2(resolvedTarget, "AGENTS.md")));
784
+ copyTemplateEntry(
785
+ join2(templateDir, "apm.config.json"),
786
+ join2(resolvedTarget, "apm.config.json")
787
+ );
788
+ const rulesDest = join2(resolvedTarget, "rules");
789
+ mkdirSync2(toFsPath(rulesDest), { recursive: true });
790
+ for (const name of ruleNames) {
791
+ copyTemplateEntry(join2(rulesSrc, name), join2(rulesDest, name));
792
+ }
793
+ assertWebIdeTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
794
+ }
780
795
 
781
796
  // src/workspace-repos.ts
782
797
  import {
@@ -2923,462 +2938,119 @@ function resolveMessageReplyFallback(fallback) {
2923
2938
  }
2924
2939
 
2925
2940
  // 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
2941
  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`
2953
- );
2954
- return false;
2955
- }
2942
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
2943
+ function applyWorkspaceName(apmDir, name) {
2944
+ const trimmedName = name?.trim();
2945
+ if (!trimmedName) return;
2956
2946
  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;
2947
+ const config = readFileSync5(apmConfigPath, "utf8");
2948
+ const configJson = JSON.parse(config);
2949
+ configJson.name = trimmedName;
2950
+ writeFileSync5(
2951
+ apmConfigPath,
2952
+ `${JSON.stringify(configJson, null, 2)}
2953
+ `,
2954
+ "utf8"
2955
+ );
2962
2956
  }
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 };
2957
+ async function initializeWorkspace(workdir, options) {
2958
+ await ensureWorkspaceApmDirForInit(workdir);
2959
+ const apmDir = workspaceApmDir(workdir);
2960
+ if (options?.webide) {
2961
+ await copyWebIdeTemplateFiles(apmDir, workdir);
2962
+ } else {
2963
+ await copyTemplateFiles(apmDir, workdir);
2996
2964
  }
2997
- const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
2998
- const wrote = await writeDeploymentConfigContent(
2999
- targetApmDir,
3000
- config.content,
3001
- config.name
2965
+ applyWorkspaceName(apmDir, options?.name);
2966
+ console.log(
2967
+ options?.webide ? `[apm] \u5DF2\u6309 WebIDE \u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}` : `[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`
3002
2968
  );
3003
- if (!wrote) {
3004
- return { synced: false, repositoryId };
2969
+ return { didInit: true };
2970
+ }
2971
+ async function ensureWorkspaceInitialized(workdir, options) {
2972
+ if (isWorkspaceApmInitialized(workdir)) {
2973
+ return { didInit: false };
3005
2974
  }
3006
- return { synced: true, repositoryId, configName: config.name };
2975
+ console.log(
2976
+ options?.webide ? `[apm] \u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u672A\u68C0\u6D4B\u5230\u5DF2\u521D\u59CB\u5316\u7684 .apm\uFF0C\u6B63\u5728\u6309 WebIDE \u81EA\u52A8\u521D\u59CB\u5316\u2026` : `[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`
2977
+ );
2978
+ return initializeWorkspace(workdir, options);
3007
2979
  }
3008
2980
 
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;
2981
+ // src/commands/connect/pre-step-cache.ts
2982
+ function sessionWorkdirKey(sessionId, workdir) {
2983
+ return `${sessionId}\0${workdir}`;
3029
2984
  }
3030
- function projectDocumentsDir(apmRoot, projectId) {
3031
- const id = normalizeProjectIdForPath(projectId);
3032
- return join5(apmRoot ?? workspaceApmDir(), "project", id);
2985
+ var lastBranchKey = null;
2986
+ function shouldRunBranch(sessionId, workdir) {
2987
+ return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
3033
2988
  }
3034
- function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
3035
- const normalized = normalizeLocalDocumentPath(documentPath);
3036
- return join5(
3037
- projectDocumentsDir(apmRoot, projectId),
3038
- ...normalized.split("/")
3039
- );
2989
+ function markBranchDone(sessionId, workdir) {
2990
+ lastBranchKey = sessionWorkdirKey(sessionId, workdir);
3040
2991
  }
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("/");
2992
+
2993
+ // src/commands/connect/webide-pull-requests.ts
2994
+ var PRE_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set([
2995
+ "generate-cases",
2996
+ "run-auto-test",
2997
+ // 验收合并前兜底确保 PR 存在
2998
+ "accept"
2999
+ ]);
3000
+ var POST_AGENT_PR_ACTIONS = /* @__PURE__ */ new Set(["create-pr", "finish-manual-test"]);
3001
+ function shouldEnsureWebIdePullRequests(action) {
3002
+ return PRE_AGENT_PR_ACTIONS.has(action);
3051
3003
  }
3052
- function hashLocalFileContent(content) {
3053
- return createHash("sha256").update(content, "utf8").digest("hex");
3004
+ function shouldEnsureWebIdePullRequestsAfterAgent(action) {
3005
+ return POST_AGENT_PR_ACTIONS.has(action);
3054
3006
  }
3055
- function readLocalManifest(apmRoot, projectId) {
3056
- const manifestPath3 = join5(
3057
- projectDocumentsDir(apmRoot, projectId),
3058
- MANIFEST_FILE
3007
+ function formatWebIdePullRequestSummary(result) {
3008
+ const lines = [];
3009
+ lines.push("## PR \u521B\u5EFA\u7ED3\u679C");
3010
+ lines.push(
3011
+ `\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
3012
  );
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 [];
3013
+ if (result.items.length === 0) {
3014
+ lines.push("\u672A\u5904\u7406\u4EFB\u4F55\u4ED3\u5E93\u3002");
3015
+ return lines.join("\n");
3075
3016
  }
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);
3017
+ lines.push("");
3018
+ for (const item of result.items) {
3019
+ if (item.outcome === "created") {
3020
+ const num = item.number != null && item.number > 0 ? `#${item.number}` : "\uFF08\u65E0\u7F16\u53F7\uFF09";
3021
+ const branch = item.headBranch && item.baseBranch ? `${item.headBranch} \u2192 ${item.baseBranch}` : item.baseBranch ? `\u76EE\u6807\u5206\u652F ${item.baseBranch}` : "";
3022
+ const state = item.state ? `\u72B6\u6001 ${item.state}` : "";
3023
+ const url = item.url?.trim() ? item.url.trim() : "";
3024
+ lines.push(
3025
+ `- **${item.label}**\uFF1A\u5DF2\u521B\u5EFA ${num}${branch ? `\uFF0C${branch}` : ""}${state ? `\uFF0C${state}` : ""}${url ? `\uFF0C${url}` : ""}`
3026
+ );
3027
+ continue;
3089
3028
  }
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);
3029
+ if (item.outcome === "skipped_no_changes") {
3030
+ lines.push(
3031
+ `- **${item.label}**\uFF1A\u76F8\u5BF9\u57FA\u7EBF ${item.baseBranch || "\uFF08\u672A\u77E5\uFF09"} \u65E0\u4EE3\u7801\u53D8\u66F4\uFF0C\u672A\u521B\u5EFA PR`
3032
+ );
3033
+ continue;
3105
3034
  }
3106
- }
3107
- const deleteLocal = [];
3108
- for (const path of localMap.keys()) {
3109
- if (!remoteMap.has(path)) {
3110
- deleteLocal.push(path);
3035
+ if (item.outcome === "skipped") {
3036
+ lines.push(`- **${item.label}**\uFF1A\u5DF2\u8DF3\u8FC7 \u2014 ${item.detail}`);
3037
+ continue;
3111
3038
  }
3039
+ lines.push(`- **${item.label}**\uFF1A\u521B\u5EFA\u5931\u8D25 \u2014 ${item.detail}`);
3112
3040
  }
3113
- return { download, deleteLocal };
3041
+ return lines.join("\n");
3114
3042
  }
3115
- async function resolveProjectIdForSync(api, workdirPath, options) {
3116
- const explicit = options?.projectId?.trim();
3117
- if (explicit) {
3118
- return { projectId: explicit, diagnostic: null };
3119
- }
3120
- 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
- }
3134
- 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`
3138
- };
3139
- } catch (err) {
3140
- const detail = err instanceof Error ? err.message : String(err);
3141
- 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;
3043
+ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3044
+ const api = createApmApiClient(cfg);
3045
+ const empty = () => ({
3046
+ ensured: 0,
3047
+ skipped: 0,
3048
+ skippedNoChanges: 0,
3049
+ failed: 0,
3050
+ items: [],
3051
+ tips: []
3052
+ });
3053
+ let repoRoots;
3382
3054
  try {
3383
3055
  const manifest = loadWorkspaceReposCache(workdir);
3384
3056
  repoRoots = resolveWorkspaceRepoAbsolutePaths(manifest);
@@ -3555,165 +3227,24 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
3555
3227
  }
3556
3228
 
3557
3229
  // src/version.ts
3558
- import { readFileSync as readFileSync7 } from "fs";
3559
- import { dirname as dirname6, join as join7 } from "path";
3230
+ import { readFileSync as readFileSync6 } from "fs";
3231
+ import { dirname as dirname5, join as join5 } from "path";
3560
3232
  import { fileURLToPath as fileURLToPath2 } from "url";
3561
3233
  function readCliVersion() {
3562
3234
  try {
3563
- const dir = dirname6(fileURLToPath2(import.meta.url));
3564
- const pkgPath = join7(dir, "..", "package.json");
3565
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
3235
+ const dir = dirname5(fileURLToPath2(import.meta.url));
3236
+ const pkgPath = join5(dir, "..", "package.json");
3237
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
3566
3238
  return pkg.version ?? "0.0.0";
3567
- } catch {
3568
- return "0.0.0";
3569
- }
3570
- }
3571
-
3572
- // 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";
3575
-
3576
- // src/skills-sync.ts
3577
- import {
3578
- copyFileSync as copyFileSync2,
3579
- cpSync,
3580
- existsSync as existsSync7,
3581
- mkdirSync as mkdirSync6,
3582
- readdirSync as readdirSync4,
3583
- rmSync as rmSync3,
3584
- statSync as statSync3,
3585
- writeFileSync as writeFileSync8
3586
- } 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");
3591
- function sanitizeSkillDirName(name) {
3592
- const trimmed = name.trim();
3593
- if (!trimmed) return "skill";
3594
- return trimmed.replace(/[/\\:*?"<>|]/g, "_");
3595
- }
3596
- 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);
3600
- return statSync3(path).isDirectory();
3601
- });
3602
- }
3603
- function syncAgentsGuide(apmDir) {
3604
- if (!existsSync7(AGENTS_TEMPLATE_PATH)) return false;
3605
- mkdirSync6(apmDir, { recursive: true });
3606
- copyFileSync2(AGENTS_TEMPLATE_PATH, join8(apmDir, "AGENTS.md"));
3607
- return true;
3608
- }
3609
- 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);
3613
- return statSync3(path).isFile();
3614
- });
3615
- }
3616
- function syncBaseRules(rulesDir) {
3617
- mkdirSync6(rulesDir, { recursive: true });
3618
- const names = listBaseRuleFileNames();
3619
- for (const name of names) {
3620
- const src = join8(BASE_RULES_TEMPLATE_DIR, name);
3621
- const dest = join8(rulesDir, name);
3622
- copyFileSync2(src, dest);
3623
- }
3624
- return names;
3625
- }
3626
- function syncBaseSkills(skillsDir) {
3627
- mkdirSync6(skillsDir, { recursive: true });
3628
- const names = listBaseSkillDirNames();
3629
- for (const name of names) {
3630
- const src = join8(BASE_SKILLS_TEMPLATE_DIR, name);
3631
- const dest = join8(skillsDir, name);
3632
- cpSync(src, dest, { recursive: true, force: true });
3633
- }
3634
- return names;
3635
- }
3636
- function syncSupplementarySkills(skillsDir, list) {
3637
- const baseNames = new Set(listBaseSkillDirNames());
3638
- const apiDirNames = /* @__PURE__ */ new Set();
3639
- const written = [];
3640
- const skipped = [];
3641
- for (const skill of list) {
3642
- const dirName = sanitizeSkillDirName(skill.name);
3643
- apiDirNames.add(dirName);
3644
- if (baseNames.has(dirName)) {
3645
- skipped.push(dirName);
3646
- continue;
3647
- }
3648
- const skillDir = join8(skillsDir, dirName);
3649
- mkdirSync6(skillDir, { recursive: true });
3650
- writeFileSync8(join8(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
3651
- written.push(dirName);
3652
- }
3653
- const removed = [];
3654
- if (!existsSync7(skillsDir)) return { written, skipped, removed };
3655
- for (const entry of readdirSync4(skillsDir)) {
3656
- const full = join8(skillsDir, entry);
3657
- if (!statSync3(full).isDirectory()) continue;
3658
- if (baseNames.has(entry)) continue;
3659
- if (apiDirNames.has(entry)) continue;
3660
- rmSync3(full, { recursive: true, force: true });
3661
- removed.push(entry);
3662
- }
3663
- return { written, skipped, removed };
3664
- }
3665
-
3666
- // src/commands/update-skills.ts
3667
- async function syncWorkspaceSkills(cfg, workdir) {
3668
- const apmDir = workspaceApmDir(workdir);
3669
- const fsApmDir = toFsPath(apmDir);
3670
- if (!existsSync8(fsApmDir)) {
3671
- throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
3672
- }
3673
- const apmStat = statSync4(fsApmDir);
3674
- if (!apmStat.isDirectory()) {
3675
- throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
3676
- }
3677
- const api = createApmApiClient(cfg);
3678
- const { list } = await api.cli.listSkills({});
3679
- if (syncAgentsGuide(apmDir)) {
3680
- console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
3681
- }
3682
- const rulesDir = join9(apmDir, "rules");
3683
- const ruleNames = syncBaseRules(rulesDir);
3684
- for (const name of ruleNames) {
3685
- console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
3686
- }
3687
- const skillsDir = join9(apmDir, "skills");
3688
- mkdirSync7(toFsPath(skillsDir), { recursive: true });
3689
- const baseNames = syncBaseSkills(skillsDir);
3690
- for (const name of baseNames) {
3691
- console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u6280\u80FD: skills/${name}/`);
3692
- }
3693
- const { written, skipped, removed } = syncSupplementarySkills(
3694
- skillsDir,
3695
- list
3696
- );
3697
- for (const name of written) {
3698
- console.log(`[apm] \u5DF2\u5199\u5165\u8865\u5145\u6280\u80FD: skills/${name}/SKILL.md`);
3699
- }
3700
- for (const name of skipped) {
3701
- console.log(
3702
- `[apm] \u5DF2\u8DF3\u8FC7\u4E0E\u57FA\u7840\u6280\u80FD\u540C\u540D\u7684\u8865\u5145\u6280\u80FD: ${name}\uFF08\u4FDD\u7559\u6A21\u677F\u7248\u672C\uFF09`
3703
- );
3704
- }
3705
- for (const name of removed) {
3706
- console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u8865\u5145\u6280\u80FD: skills/${name}/`);
3239
+ } catch {
3240
+ return "0.0.0";
3707
3241
  }
3708
- console.log(
3709
- `[apm] \u540C\u6B65\u5B8C\u6210\uFF1A${ruleNames.length} \u4E2A\u57FA\u7840\u89C4\u5219\uFF0C${baseNames.length} \u4E2A\u57FA\u7840\u6280\u80FD\uFF0C${written.length} \u4E2A\u8865\u5145\u6280\u80FD`
3710
- );
3711
3242
  }
3712
3243
 
3713
3244
  // 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";
3245
+ import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
3246
+ import { join as join6 } from "path";
3247
+ var MANIFEST_FILE = ".sync-manifest.json";
3717
3248
  async function downloadAttachment(cfg, attachmentId) {
3718
3249
  const base = cfg.baseUrl.trim().replace(/\/+$/, "");
3719
3250
  const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
@@ -3728,13 +3259,13 @@ async function downloadAttachment(cfg, attachmentId) {
3728
3259
  return Buffer.from(await res.arrayBuffer());
3729
3260
  }
3730
3261
  function loadManifest(dir) {
3731
- const path = join10(dir, MANIFEST_FILE2);
3732
- if (!existsSync9(path)) {
3262
+ const path = join6(dir, MANIFEST_FILE);
3263
+ if (!existsSync6(path)) {
3733
3264
  return { version: 1, attachments: {} };
3734
3265
  }
3735
3266
  try {
3736
3267
  const parsed = JSON.parse(
3737
- readFileSync8(path, "utf8")
3268
+ readFileSync7(path, "utf8")
3738
3269
  );
3739
3270
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
3740
3271
  return parsed;
@@ -3744,15 +3275,15 @@ function loadManifest(dir) {
3744
3275
  return { version: 1, attachments: {} };
3745
3276
  }
3746
3277
  function saveManifest(dir, manifest) {
3747
- writeFileSync9(
3748
- join10(dir, MANIFEST_FILE2),
3278
+ writeFileSync6(
3279
+ join6(dir, MANIFEST_FILE),
3749
3280
  `${JSON.stringify(manifest, null, 2)}
3750
3281
  `,
3751
3282
  "utf8"
3752
3283
  );
3753
3284
  }
3754
3285
  function isAttachmentUpToDate(entry, item, dest) {
3755
- if (!entry || !existsSync9(dest)) return false;
3286
+ if (!entry || !existsSync6(dest)) return false;
3756
3287
  if (entry.name !== item.name) return false;
3757
3288
  const createdAt = item.createdAt ?? "";
3758
3289
  return entry.createdAt === createdAt;
@@ -3767,7 +3298,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
3767
3298
  const nextManifest = { version: 1, attachments: {} };
3768
3299
  const names = [];
3769
3300
  for (const item of attachments) {
3770
- const dest = join10(dir, item.name);
3301
+ const dest = join6(dir, item.name);
3771
3302
  const entry = manifest.attachments[item.id];
3772
3303
  const createdAt = item.createdAt ?? "";
3773
3304
  names.push(item.name);
@@ -3777,7 +3308,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
3777
3308
  continue;
3778
3309
  }
3779
3310
  const buffer = await downloadAttachment(cfg, item.id);
3780
- writeFileSync9(dest, buffer);
3311
+ writeFileSync6(dest, buffer);
3781
3312
  nextManifest.attachments[item.id] = {
3782
3313
  name: item.name,
3783
3314
  createdAt
@@ -3797,16 +3328,283 @@ async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
3797
3328
  );
3798
3329
  }
3799
3330
 
3331
+ // src/project-documents-sync.ts
3332
+ import {
3333
+ existsSync as existsSync7,
3334
+ readdirSync as readdirSync3,
3335
+ readFileSync as readFileSync8,
3336
+ rmSync as rmSync2,
3337
+ writeFileSync as writeFileSync7
3338
+ } from "fs";
3339
+ import { createHash } from "crypto";
3340
+ import { dirname as dirname6, join as join7, relative as relative2, sep } from "path";
3341
+ var MANIFEST_FILE2 = "manifest.json";
3342
+ function normalizeProjectIdForPath(projectId) {
3343
+ const id = projectId.trim();
3344
+ if (!id) {
3345
+ throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
3346
+ }
3347
+ if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
3348
+ throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
3349
+ }
3350
+ return id;
3351
+ }
3352
+ function projectDocumentsDir(apmRoot, projectId) {
3353
+ const id = normalizeProjectIdForPath(projectId);
3354
+ return join7(apmRoot ?? workspaceApmDir(), "project", id);
3355
+ }
3356
+ function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
3357
+ const normalized = normalizeLocalDocumentPath(documentPath);
3358
+ return join7(
3359
+ projectDocumentsDir(apmRoot, projectId),
3360
+ ...normalized.split("/")
3361
+ );
3362
+ }
3363
+ function normalizeLocalDocumentPath(path) {
3364
+ const trimmed = path.trim().replace(/\\/g, "/");
3365
+ if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
3366
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path}`);
3367
+ }
3368
+ const segments = trimmed.split("/").filter(Boolean);
3369
+ if (segments.some((segment) => segment === ".." || segment === ".")) {
3370
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path}`);
3371
+ }
3372
+ return segments.join("/");
3373
+ }
3374
+ function hashLocalFileContent(content) {
3375
+ return createHash("sha256").update(content, "utf8").digest("hex");
3376
+ }
3377
+ function readLocalManifest(apmRoot, projectId) {
3378
+ const manifestPath3 = join7(
3379
+ projectDocumentsDir(apmRoot, projectId),
3380
+ MANIFEST_FILE2
3381
+ );
3382
+ if (!existsSync7(manifestPath3)) {
3383
+ return null;
3384
+ }
3385
+ try {
3386
+ return JSON.parse(
3387
+ readFileSync8(manifestPath3, "utf8")
3388
+ );
3389
+ } catch {
3390
+ return null;
3391
+ }
3392
+ }
3393
+ function listLocalDocumentPaths(apmRoot, projectId) {
3394
+ const root = projectDocumentsDir(apmRoot, projectId);
3395
+ if (!existsSync7(root)) {
3396
+ return [];
3397
+ }
3398
+ const paths = [];
3399
+ const walk = (dir) => {
3400
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
3401
+ const abs = join7(dir, entry.name);
3402
+ if (entry.isDirectory()) {
3403
+ walk(abs);
3404
+ continue;
3405
+ }
3406
+ if (entry.isFile() && entry.name === MANIFEST_FILE2) {
3407
+ continue;
3408
+ }
3409
+ const rel = relative2(root, abs).split(sep).join("/");
3410
+ paths.push(rel);
3411
+ }
3412
+ };
3413
+ walk(root);
3414
+ return paths.sort();
3415
+ }
3416
+ function diffManifestPaths(remote, local) {
3417
+ const remoteMap = new Map(
3418
+ (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3419
+ );
3420
+ const localMap = new Map(
3421
+ (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3422
+ );
3423
+ const download = [];
3424
+ for (const [path, hash] of remoteMap) {
3425
+ if (localMap.get(path) !== hash) {
3426
+ download.push(path);
3427
+ }
3428
+ }
3429
+ const deleteLocal = [];
3430
+ for (const path of localMap.keys()) {
3431
+ if (!remoteMap.has(path)) {
3432
+ deleteLocal.push(path);
3433
+ }
3434
+ }
3435
+ return { download, deleteLocal };
3436
+ }
3437
+ async function resolveProjectIdForSync(api, workdirPath, options) {
3438
+ const explicit = options?.projectId?.trim();
3439
+ if (explicit) {
3440
+ return { projectId: explicit, diagnostic: null };
3441
+ }
3442
+ try {
3443
+ const baseline = await api.cli.workspaceBaseline({ workdirPath });
3444
+ const projectId = baseline.projectId?.trim() || null;
3445
+ if (projectId) {
3446
+ return { projectId, diagnostic: null };
3447
+ }
3448
+ const projectIds = baseline.projectIds ?? [];
3449
+ if (projectIds.length === 0) {
3450
+ return {
3451
+ projectId: null,
3452
+ 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
3453
+ \u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
3454
+ };
3455
+ }
3456
+ return {
3457
+ projectId: null,
3458
+ diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
3459
+ \u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
3460
+ };
3461
+ } catch (err) {
3462
+ const detail = err instanceof Error ? err.message : String(err);
3463
+ return {
3464
+ projectId: null,
3465
+ diagnostic: detail.replace(/^\[apm\]\s*/, "")
3466
+ };
3467
+ }
3468
+ }
3469
+ async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
3470
+ const empty = {
3471
+ synced: false,
3472
+ projectId: null,
3473
+ downloaded: 0,
3474
+ deleted: 0
3475
+ };
3476
+ const cfg = await tryReadApmConfig();
3477
+ if (!cfg || !resolveApiKey(cfg)) {
3478
+ console.log(
3479
+ "[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"
3480
+ );
3481
+ return empty;
3482
+ }
3483
+ const api = createApmApiClient(cfg);
3484
+ const { projectId, diagnostic } = await resolveProjectIdForSync(
3485
+ api,
3486
+ workdirPath,
3487
+ options
3488
+ );
3489
+ if (!projectId) {
3490
+ console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
3491
+ ${diagnostic ?? ""}`);
3492
+ return empty;
3493
+ }
3494
+ const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
3495
+ const docsDir = projectDocumentsDir(targetApmDir, projectId);
3496
+ await ensureDirExists(docsDir);
3497
+ const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
3498
+ { projectId }
3499
+ );
3500
+ if (!remoteManifest) {
3501
+ console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
3502
+ return { ...empty, projectId };
3503
+ }
3504
+ const localManifest = readLocalManifest(targetApmDir, projectId);
3505
+ const { download, deleteLocal } = diffManifestPaths(
3506
+ remoteManifest,
3507
+ localManifest
3508
+ );
3509
+ let downloaded = 0;
3510
+ if (download.length > 0) {
3511
+ const { list } = await api.cli.listProjectDocuments({
3512
+ projectId,
3513
+ paths: download.join(",")
3514
+ });
3515
+ for (const doc of list) {
3516
+ const absPath = toFsPath(
3517
+ projectDocumentLocalPath(targetApmDir, projectId, doc.path)
3518
+ );
3519
+ await ensureDirExists(dirname6(absPath));
3520
+ writeFileSync7(absPath, doc.content, "utf8");
3521
+ downloaded += 1;
3522
+ }
3523
+ }
3524
+ let deleted = 0;
3525
+ for (const path of deleteLocal) {
3526
+ const absPath = toFsPath(
3527
+ projectDocumentLocalPath(targetApmDir, projectId, path)
3528
+ );
3529
+ if (existsSync7(absPath)) {
3530
+ rmSync2(absPath, { force: true });
3531
+ deleted += 1;
3532
+ }
3533
+ }
3534
+ writeFileSync7(
3535
+ toFsPath(join7(docsDir, MANIFEST_FILE2)),
3536
+ `${JSON.stringify(remoteManifest, null, 2)}
3537
+ `,
3538
+ "utf8"
3539
+ );
3540
+ console.log(
3541
+ `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
3542
+ );
3543
+ return {
3544
+ synced: true,
3545
+ projectId,
3546
+ downloaded,
3547
+ deleted
3548
+ };
3549
+ }
3550
+ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
3551
+ const api = createApmApiClient(cfg);
3552
+ const { projectId } = await resolveProjectIdForSync(
3553
+ api,
3554
+ workdirPath,
3555
+ options
3556
+ );
3557
+ if (!projectId) {
3558
+ return 0;
3559
+ }
3560
+ const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
3561
+ const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
3562
+ if (localPaths.length === 0) {
3563
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3564
+ return 0;
3565
+ }
3566
+ const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
3567
+ const remoteHashByPath = new Map(
3568
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3569
+ );
3570
+ const remoteDescriptionByPath = new Map(
3571
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
3572
+ );
3573
+ let synced = 0;
3574
+ for (const path of localPaths) {
3575
+ const absPath = toFsPath(
3576
+ projectDocumentLocalPath(targetApmDir, projectId, path)
3577
+ );
3578
+ const content = readFileSync8(absPath, "utf8");
3579
+ const contentHash = hashLocalFileContent(content);
3580
+ if (remoteHashByPath.get(path) === contentHash) {
3581
+ continue;
3582
+ }
3583
+ await api.cli.upsertProjectDocument({
3584
+ projectId,
3585
+ path,
3586
+ content,
3587
+ description: remoteDescriptionByPath.get(path) ?? void 0
3588
+ });
3589
+ synced += 1;
3590
+ console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path}`);
3591
+ }
3592
+ if (synced === 0) {
3593
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
3594
+ }
3595
+ return synced;
3596
+ }
3597
+
3800
3598
  // 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";
3599
+ import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
3600
+ import { join as join8 } from "path";
3803
3601
  var CLI_VERSION_FILE = ".cli-version.json";
3804
3602
  function manifestPath2(apmDir) {
3805
- return join11(apmDir, CLI_VERSION_FILE);
3603
+ return join8(apmDir, CLI_VERSION_FILE);
3806
3604
  }
3807
3605
  function loadManifest2(apmDir) {
3808
3606
  const path = toFsPath(manifestPath2(apmDir));
3809
- if (!existsSync10(path)) {
3607
+ if (!existsSync8(path)) {
3810
3608
  return null;
3811
3609
  }
3812
3610
  try {
@@ -3822,7 +3620,7 @@ function loadManifest2(apmDir) {
3822
3620
  }
3823
3621
  function saveManifest2(apmDir, cliVersion) {
3824
3622
  const manifest = { version: 1, cliVersion };
3825
- writeFileSync10(
3623
+ writeFileSync8(
3826
3624
  toFsPath(manifestPath2(apmDir)),
3827
3625
  `${JSON.stringify(manifest, null, 2)}
3828
3626
  `,
@@ -3928,7 +3726,9 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3928
3726
  await runPrepStep(
3929
3727
  "\u521D\u59CB\u5316\u5DE5\u4F5C\u533A",
3930
3728
  async () => {
3931
- const { didInit } = await ensureWorkspaceInitialized(workdir);
3729
+ const { didInit } = await ensureWorkspaceInitialized(workdir, {
3730
+ webide: true
3731
+ });
3932
3732
  return didInit;
3933
3733
  },
3934
3734
  (didInit) => didInit ? "\u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A" : "\u5DE5\u4F5C\u533A\u5DF2\u5C31\u7EEA"
@@ -3937,9 +3737,9 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3937
3737
  if (shouldSyncSkillsForCliVersion(workdir, cliVersion)) {
3938
3738
  if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
3939
3739
  await runPrepStep(
3940
- "\u540C\u6B65 Skills",
3740
+ "\u540C\u6B65 WebIDE \u89C4\u5219",
3941
3741
  async () => {
3942
- await syncWorkspaceSkills(cfg, workdir);
3742
+ await copyWebIdeTemplateFiles(workspaceApmDir(workdir), workdir);
3943
3743
  markSkillsSyncedForCliVersion(workdir, cliVersion);
3944
3744
  return cliVersion;
3945
3745
  },
@@ -3947,7 +3747,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal, options) {
3947
3747
  );
3948
3748
  } else {
3949
3749
  eventSession.addCliStep(
3950
- "\u540C\u6B65 Skills",
3750
+ "\u540C\u6B65 WebIDE \u89C4\u5219",
3951
3751
  `\u7248\u672C\u4E00\u81F4\uFF08${cliVersion}\uFF09\uFF0C\u8DF3\u8FC7`,
3952
3752
  "skip"
3953
3753
  );