ai-project-manage-cli 4.0.21 → 4.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -154,7 +154,33 @@ var requestConfig = {
154
154
  path: "/requirement-artifacts/delete"
155
155
  })
156
156
  },
157
+ theaterSessionArtifact: {
158
+ upsert: defineEndpoint({
159
+ method: "POST",
160
+ path: "/theater-session-artifacts/upsert"
161
+ }),
162
+ create: defineEndpoint({
163
+ method: "POST",
164
+ path: "/theater-session-artifacts/create"
165
+ }),
166
+ update: defineEndpoint({
167
+ method: "POST",
168
+ path: "/theater-session-artifacts/update"
169
+ }),
170
+ list: defineEndpoint({
171
+ method: "GET",
172
+ path: "/theater-session-artifacts/list"
173
+ }),
174
+ get: defineEndpoint({
175
+ method: "GET",
176
+ path: "/theater-session-artifacts/get"
177
+ })
178
+ },
157
179
  theater: {
180
+ sessionBranchBaseline: defineEndpoint({
181
+ method: "GET",
182
+ path: "/theater/sessions/branch-baseline"
183
+ }),
158
184
  reportMemberAgentProgress: defineEndpoint({
159
185
  method: "POST",
160
186
  path: "/theater/sessions/report-member-agent-progress"
@@ -203,6 +229,20 @@ var WORKSPACE_APM_DIR = resolve(process.cwd(), ".apm");
203
229
  function requirementWorkitemsDir(requirementId, workspaceDir = WORKSPACE_APM_DIR) {
204
230
  return join2(workspaceDir, "workitems", requirementId);
205
231
  }
232
+ function theaterSessionDir(sessionId, apmRoot = WORKSPACE_APM_DIR) {
233
+ return join2(apmRoot, "theater-sessions", sessionId);
234
+ }
235
+ function normalizeTheaterArtifactFileName(fileName) {
236
+ return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/^docs\//, "");
237
+ }
238
+ function theaterArtifactLocalRelativePath(fileName) {
239
+ const base = normalizeTheaterArtifactFileName(fileName);
240
+ return `docs/${base}`;
241
+ }
242
+ function theaterSessionDocumentPath(sessionId, fileName = "prd.md", apmRoot = WORKSPACE_APM_DIR) {
243
+ const localRelative = theaterArtifactLocalRelativePath(fileName);
244
+ return join2(theaterSessionDir(sessionId, apmRoot), localRelative);
245
+ }
206
246
  async function ensureLoggedConfig() {
207
247
  const cfg = await ensureApmConfig();
208
248
  if (!cfg.token) {
@@ -377,7 +417,7 @@ async function runComment(requirementId, file, options) {
377
417
  import { randomUUID as randomUUID2 } from "crypto";
378
418
  import WebSocket from "ws";
379
419
  import { Agent } from "@cursor/sdk";
380
- import { join as join6 } from "path";
420
+ import { join as join7 } from "path";
381
421
 
382
422
  // src/session-utils.ts
383
423
  import { appendFileSync } from "fs";
@@ -780,6 +820,243 @@ async function runPull(requirementId, workspaceDir) {
780
820
  return WORKITEMS_DIR;
781
821
  }
782
822
 
823
+ // src/commands/pull-theater-session.ts
824
+ import { writeFileSync as writeFileSync4 } from "fs";
825
+ import { dirname as dirname2, join as join6 } from "path";
826
+ function normalizeRelativePath(fileName) {
827
+ return theaterArtifactLocalRelativePath(fileName);
828
+ }
829
+ async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
830
+ const trimmedSessionId = sessionId.trim();
831
+ if (!trimmedSessionId) {
832
+ throw new Error("sessionId \u4E0D\u80FD\u4E3A\u7A7A");
833
+ }
834
+ const cfg = await ensureLoggedConfig();
835
+ const api = createApmApiClient(cfg);
836
+ const sessionDir = theaterSessionDir(trimmedSessionId, apmRoot);
837
+ await ensureDirExists(sessionDir);
838
+ await ensureDirExists(join6(sessionDir, "docs"));
839
+ const pageSize = 500;
840
+ let page = 1;
841
+ const items = [];
842
+ while (true) {
843
+ const batch = await api.theaterSessionArtifact.list({
844
+ sessionId: trimmedSessionId,
845
+ page,
846
+ pageSize
847
+ });
848
+ items.push(...batch.items);
849
+ if (batch.total === 0 || items.length >= batch.total) break;
850
+ page += 1;
851
+ }
852
+ for (const item of items) {
853
+ const art = await api.theaterSessionArtifact.get({
854
+ sessionId: trimmedSessionId,
855
+ artifactId: item.id
856
+ });
857
+ const relativePath = normalizeRelativePath(art.fileName);
858
+ const destPath = join6(sessionDir, relativePath);
859
+ await ensureDirExists(dirname2(destPath));
860
+ writeFileSync4(destPath, art.content, "utf8");
861
+ console.log(`[apm] \u5DF2\u8986\u76D6\u4F1A\u8BDD\u6587\u6863: ${relativePath}`);
862
+ }
863
+ return sessionDir;
864
+ }
865
+
866
+ // src/commands/branch.ts
867
+ import { execFile } from "child_process";
868
+ import { resolve as resolve3 } from "path";
869
+ import { promisify } from "util";
870
+ var execFileAsync = promisify(execFile);
871
+ async function fetchBaselineBranchFromApi(requirementId, cwd) {
872
+ const cfg = await ensureLoggedConfig();
873
+ const api = createApmApiClient(cfg);
874
+ const workdirPath = resolve3(cwd);
875
+ const res = await api.cliRequirements.branchBaseline({
876
+ requirementId,
877
+ workdirPath
878
+ });
879
+ if (!res.repositoryId) {
880
+ throw new Error(
881
+ `[apm] \u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}
882
+ \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`
883
+ );
884
+ }
885
+ const name = (res.defaultBranch ?? "").trim();
886
+ if (!name) {
887
+ throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
888
+ }
889
+ return name;
890
+ }
891
+ async function fetchTheaterBaselineBranchFromApi(sessionId, cwd) {
892
+ const cfg = await ensureLoggedConfig();
893
+ const api = createApmApiClient(cfg);
894
+ const workdirPath = resolve3(cwd);
895
+ const res = await api.theater.sessionBranchBaseline({
896
+ sessionId,
897
+ workdirPath
898
+ });
899
+ if (!res.repositoryId) {
900
+ throw new Error(
901
+ `[apm] \u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}
902
+ \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`
903
+ );
904
+ }
905
+ const name = (res.defaultBranch ?? "").trim();
906
+ if (!name) {
907
+ throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
908
+ }
909
+ return name;
910
+ }
911
+ function branchNameForRequirement(requirementId) {
912
+ const id = requirementId.trim();
913
+ if (!id) {
914
+ throw new Error("[apm] \u9700\u6C42 ID \u4E0D\u80FD\u4E3A\u7A7A");
915
+ }
916
+ if (/[\s/\\]/.test(id)) {
917
+ throw new Error(
918
+ "[apm] \u9700\u6C42 ID \u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u6216\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BCD\u3001\u6570\u5B57\u3001._- \u7B49"
919
+ );
920
+ }
921
+ return `feat/req-${id}`;
922
+ }
923
+ var branchNameForTheaterSession = branchNameForRequirement;
924
+ async function execGit(cwd, args, quiet) {
925
+ try {
926
+ const { stdout, stderr } = await execFileAsync("git", args, {
927
+ cwd,
928
+ encoding: "utf8",
929
+ maxBuffer: 10 * 1024 * 1024
930
+ });
931
+ if (!quiet && stderr.trim()) {
932
+ process.stderr.write(stderr);
933
+ }
934
+ return stdout;
935
+ } catch (err) {
936
+ const e = err;
937
+ const detail = (e.stderr ?? e.message ?? String(err)).trim();
938
+ throw new Error(
939
+ `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
940
+ );
941
+ }
942
+ }
943
+ async function ensureGitRepo(cwd) {
944
+ await execGit(cwd, ["rev-parse", "--git-dir"], true);
945
+ }
946
+ async function getCurrentBranch(cwd) {
947
+ const name = (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
948
+ return name;
949
+ }
950
+ async function isWorkingTreeDirty(cwd) {
951
+ const out = await execGit(cwd, ["status", "--porcelain"], true);
952
+ return out.trim().length > 0;
953
+ }
954
+ async function remoteHeadBranchExists(cwd, branch) {
955
+ const out = await execGit(
956
+ cwd,
957
+ ["ls-remote", "--heads", "origin", branch],
958
+ true
959
+ );
960
+ return out.trim().length > 0;
961
+ }
962
+ async function localBranchExists(cwd, branch) {
963
+ try {
964
+ await execGit(
965
+ cwd,
966
+ ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
967
+ true
968
+ );
969
+ return true;
970
+ } catch {
971
+ return false;
972
+ }
973
+ }
974
+ function theaterSessionCommitMessage(sessionId, memberKey) {
975
+ const id = sessionId.trim();
976
+ const key = memberKey?.trim();
977
+ return key ? `feat(req-${id}): ${key} theater agent` : `feat(req-${id}): theater agent`;
978
+ }
979
+ async function commitWorkingTreeIfDirty(cwd, message) {
980
+ await ensureGitRepo(cwd);
981
+ if (!await isWorkingTreeDirty(cwd)) {
982
+ return false;
983
+ }
984
+ const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
985
+ await execGit(cwd, ["add", "-A"]);
986
+ await execGit(cwd, ["commit", "-m", commitMessage]);
987
+ console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
988
+ return true;
989
+ }
990
+ async function ensureFeatureBranch(branch, options, fetchBaseline) {
991
+ const cwd = options.cwd ?? process.cwd();
992
+ const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
993
+ await ensureGitRepo(cwd);
994
+ const current = await getCurrentBranch(cwd);
995
+ const dirty = await isWorkingTreeDirty(cwd);
996
+ if (dirty) {
997
+ if (current === branch) {
998
+ await commitWorkingTreeIfDirty(cwd, commitMessage);
999
+ } else {
1000
+ await execGit(cwd, [
1001
+ "stash",
1002
+ "push",
1003
+ "-u",
1004
+ "-m",
1005
+ `apm: switch to ${branch}`
1006
+ ]);
1007
+ }
1008
+ }
1009
+ const remoteExists = await remoteHeadBranchExists(cwd, branch);
1010
+ if (remoteExists) {
1011
+ await execGit(cwd, ["fetch", "origin", branch]);
1012
+ const hasLocal = await localBranchExists(cwd, branch);
1013
+ if (hasLocal) {
1014
+ await execGit(cwd, ["checkout", branch]);
1015
+ await execGit(cwd, ["pull", "--no-edit"]);
1016
+ } else {
1017
+ await execGit(cwd, ["checkout", "-b", branch, `origin/${branch}`]);
1018
+ }
1019
+ } else {
1020
+ const onBranch = await getCurrentBranch(cwd) === branch;
1021
+ if (!onBranch) {
1022
+ const hasLocal = await localBranchExists(cwd, branch);
1023
+ if (hasLocal) {
1024
+ await execGit(cwd, ["checkout", branch]);
1025
+ } else {
1026
+ const baselineBranch = await fetchBaseline();
1027
+ await execGit(cwd, ["fetch", "origin", baselineBranch]);
1028
+ await execGit(cwd, [
1029
+ "checkout",
1030
+ "-b",
1031
+ branch,
1032
+ `origin/${baselineBranch}`
1033
+ ]);
1034
+ }
1035
+ }
1036
+ await execGit(cwd, ["push", "-u", "origin", branch]);
1037
+ }
1038
+ console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
1039
+ return branch;
1040
+ }
1041
+ async function runBranch(requirementId, options = {}) {
1042
+ const cwd = options.cwd ?? process.cwd();
1043
+ const branch = branchNameForRequirement(requirementId);
1044
+ return ensureFeatureBranch(
1045
+ branch,
1046
+ options,
1047
+ () => fetchBaselineBranchFromApi(requirementId, cwd)
1048
+ );
1049
+ }
1050
+ async function runTheaterSessionBranch(sessionId, options = {}) {
1051
+ const cwd = options.cwd ?? process.cwd();
1052
+ const branch = branchNameForTheaterSession(sessionId);
1053
+ return ensureFeatureBranch(
1054
+ branch,
1055
+ options,
1056
+ () => fetchTheaterBaselineBranchFromApi(sessionId, cwd)
1057
+ );
1058
+ }
1059
+
783
1060
  // src/theater-job-report.ts
784
1061
  import { randomUUID } from "crypto";
785
1062
  function extractErrorMessage(err) {
@@ -987,6 +1264,7 @@ async function handleTheaterJob(api, ws, payload) {
987
1264
  throw new Error("THEATER_JOB \u7F3A\u5C11 memberKey");
988
1265
  }
989
1266
  console.log("[apm] \u5267\u573A\u4F1A\u8BDD:", sessionId, "\u6210\u5458:", memberKey);
1267
+ const apmRoot = join7(payload.cwd, ".apm");
990
1268
  let agentId = payload.agentId?.trim() || void 0;
991
1269
  try {
992
1270
  await reportTheaterProgress(api, ws, { sessionId, memberKey, logId });
@@ -1002,11 +1280,56 @@ async function handleTheaterJob(api, ws, payload) {
1002
1280
  });
1003
1281
  return;
1004
1282
  }
1283
+ try {
1284
+ await runTheaterSessionBranch(sessionId, { cwd: payload.cwd });
1285
+ } catch (branchErr) {
1286
+ logTheaterJobError("\u5207\u6362\u5206\u652F", branchErr, { sessionId, memberKey });
1287
+ await submitTheaterFailure(api, ws, {
1288
+ sessionId,
1289
+ memberKey,
1290
+ logId,
1291
+ agentId,
1292
+ stage: "\u5207\u6362\u5206\u652F",
1293
+ error: extractErrorMessage(branchErr)
1294
+ });
1295
+ return;
1296
+ }
1297
+ try {
1298
+ await runPullTheaterSession(sessionId, apmRoot);
1299
+ } catch (pullErr) {
1300
+ logTheaterJobError("\u62C9\u53D6\u4F1A\u8BDD\u6587\u6863", pullErr, { sessionId, memberKey });
1301
+ await submitTheaterFailure(api, ws, {
1302
+ sessionId,
1303
+ memberKey,
1304
+ logId,
1305
+ agentId,
1306
+ stage: "\u62C9\u53D6\u4F1A\u8BDD\u6587\u6863",
1307
+ error: extractErrorMessage(pullErr)
1308
+ });
1309
+ return;
1310
+ }
1005
1311
  try {
1006
1312
  const eventSession = new EventSession(payload.prompt);
1007
1313
  const agent = await createAgentForJob(payload);
1008
1314
  const runResult = await runAgentStream(payload, eventSession, agent);
1009
1315
  agentId = runResult.agentId;
1316
+ try {
1317
+ await commitWorkingTreeIfDirty(
1318
+ payload.cwd,
1319
+ theaterSessionCommitMessage(sessionId, memberKey)
1320
+ );
1321
+ } catch (commitErr) {
1322
+ logTheaterJobError("\u63D0\u4EA4\u5DE5\u4F5C\u533A", commitErr, { sessionId, memberKey });
1323
+ await submitTheaterFailure(api, ws, {
1324
+ sessionId,
1325
+ memberKey,
1326
+ logId,
1327
+ agentId,
1328
+ stage: "\u63D0\u4EA4\u5DE5\u4F5C\u533A",
1329
+ error: extractErrorMessage(commitErr)
1330
+ });
1331
+ return;
1332
+ }
1010
1333
  const assistantText = eventSession.getAssistantText();
1011
1334
  const content = runResult.failed ? runResult.failReason || assistantText || "\u672A\u77E5\u9519\u8BEF" : assistantText || "[Agent \u672A\u4EA7\u751F\u6587\u672C\u8F93\u51FA]";
1012
1335
  await submitTheaterResult(api, ws, {
@@ -1037,7 +1360,7 @@ async function handleTheaterJob(api, ws, payload) {
1037
1360
  }
1038
1361
  }
1039
1362
  async function handleWorkflowJob(api, payload) {
1040
- const apmRoot = join6(payload.cwd, ".apm");
1363
+ const apmRoot = join7(payload.cwd, ".apm");
1041
1364
  console.log("[apm] ROOT:", apmRoot);
1042
1365
  console.log("[apm] workflow node:", payload.nodeId);
1043
1366
  try {
@@ -1163,17 +1486,17 @@ function runConnect(opts) {
1163
1486
  }
1164
1487
 
1165
1488
  // src/commands/init.ts
1166
- import { join as join7 } from "path";
1167
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
1489
+ import { join as join8 } from "path";
1490
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
1168
1491
  async function runInit(name) {
1169
1492
  await ensureWorkspaceApmDirForInit();
1170
1493
  await copyTemplateFiles(WORKSPACE_APM_DIR);
1171
1494
  if (name) {
1172
- const apmConfigPath = join7(WORKSPACE_APM_DIR, "apm.config.json");
1495
+ const apmConfigPath = join8(WORKSPACE_APM_DIR, "apm.config.json");
1173
1496
  const config = readFileSync4(apmConfigPath, "utf8");
1174
1497
  const configJson = JSON.parse(config);
1175
1498
  configJson.name = name;
1176
- writeFileSync4(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
1499
+ writeFileSync5(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
1177
1500
  }
1178
1501
  }
1179
1502
 
@@ -1222,156 +1545,12 @@ async function runLogin(opts) {
1222
1545
  console.log(JSON.stringify({ userId: cfg.userId, baseUrl: cfg.baseUrl }, null, 2));
1223
1546
  }
1224
1547
 
1225
- // src/commands/branch.ts
1226
- import { execFile } from "child_process";
1227
- import { resolve as resolve3 } from "path";
1228
- import { promisify } from "util";
1229
- var execFileAsync = promisify(execFile);
1230
- async function fetchBaselineBranchFromApi(requirementId, cwd) {
1231
- const cfg = await ensureLoggedConfig();
1232
- const api = createApmApiClient(cfg);
1233
- const workdirPath = resolve3(cwd);
1234
- const res = await api.cliRequirements.branchBaseline({
1235
- requirementId,
1236
- workdirPath
1237
- });
1238
- if (!res.repositoryId) {
1239
- throw new Error(
1240
- `[apm] \u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}
1241
- \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`
1242
- );
1243
- }
1244
- const name = (res.defaultBranch ?? "").trim();
1245
- if (!name) {
1246
- throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
1247
- }
1248
- return name;
1249
- }
1250
- function branchNameForRequirement(requirementId) {
1251
- const id = requirementId.trim();
1252
- if (!id) {
1253
- throw new Error("[apm] \u9700\u6C42 ID \u4E0D\u80FD\u4E3A\u7A7A");
1254
- }
1255
- if (/[\s/\\]/.test(id)) {
1256
- throw new Error(
1257
- "[apm] \u9700\u6C42 ID \u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u6216\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BCD\u3001\u6570\u5B57\u3001._- \u7B49"
1258
- );
1259
- }
1260
- return `feat/req-${id}`;
1261
- }
1262
- async function execGit(cwd, args, quiet) {
1263
- try {
1264
- const { stdout, stderr } = await execFileAsync("git", args, {
1265
- cwd,
1266
- encoding: "utf8",
1267
- maxBuffer: 10 * 1024 * 1024
1268
- });
1269
- if (!quiet && stderr.trim()) {
1270
- process.stderr.write(stderr);
1271
- }
1272
- return stdout;
1273
- } catch (err) {
1274
- const e = err;
1275
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
1276
- throw new Error(
1277
- `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
1278
- );
1279
- }
1280
- }
1281
- async function ensureGitRepo(cwd) {
1282
- await execGit(cwd, ["rev-parse", "--git-dir"], true);
1283
- }
1284
- async function getCurrentBranch(cwd) {
1285
- const name = (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1286
- return name;
1287
- }
1288
- async function isWorkingTreeDirty(cwd) {
1289
- const out = await execGit(cwd, ["status", "--porcelain"], true);
1290
- return out.trim().length > 0;
1291
- }
1292
- async function remoteHeadBranchExists(cwd, branch) {
1293
- const out = await execGit(
1294
- cwd,
1295
- ["ls-remote", "--heads", "origin", branch],
1296
- true
1297
- );
1298
- return out.trim().length > 0;
1299
- }
1300
- async function localBranchExists(cwd, branch) {
1301
- try {
1302
- await execGit(
1303
- cwd,
1304
- ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
1305
- true
1306
- );
1307
- return true;
1308
- } catch {
1309
- return false;
1310
- }
1311
- }
1312
- async function runBranch(requirementId, options = {}) {
1313
- const cwd = options.cwd ?? process.cwd();
1314
- const branch = branchNameForRequirement(requirementId);
1315
- const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
1316
- await ensureGitRepo(cwd);
1317
- const current = await getCurrentBranch(cwd);
1318
- const dirty = await isWorkingTreeDirty(cwd);
1319
- if (dirty) {
1320
- if (current === branch) {
1321
- await execGit(cwd, ["add", "-A"]);
1322
- await execGit(cwd, ["commit", "-m", commitMessage]);
1323
- } else {
1324
- await execGit(cwd, [
1325
- "stash",
1326
- "push",
1327
- "-u",
1328
- "-m",
1329
- `apm: switch to ${branch}`
1330
- ]);
1331
- }
1332
- }
1333
- const remoteExists = await remoteHeadBranchExists(cwd, branch);
1334
- if (remoteExists) {
1335
- await execGit(cwd, ["fetch", "origin", branch]);
1336
- const hasLocal = await localBranchExists(cwd, branch);
1337
- if (hasLocal) {
1338
- await execGit(cwd, ["checkout", branch]);
1339
- await execGit(cwd, ["pull", "--no-edit"]);
1340
- } else {
1341
- await execGit(cwd, ["checkout", "-b", branch, `origin/${branch}`]);
1342
- }
1343
- } else {
1344
- const onBranch = await getCurrentBranch(cwd) === branch;
1345
- if (!onBranch) {
1346
- const hasLocal = await localBranchExists(cwd, branch);
1347
- if (hasLocal) {
1348
- await execGit(cwd, ["checkout", branch]);
1349
- } else {
1350
- const baselineBranch = await fetchBaselineBranchFromApi(
1351
- requirementId,
1352
- cwd
1353
- );
1354
- await execGit(cwd, ["fetch", "origin", baselineBranch]);
1355
- await execGit(cwd, [
1356
- "checkout",
1357
- "-b",
1358
- branch,
1359
- `origin/${baselineBranch}`
1360
- ]);
1361
- }
1362
- }
1363
- await execGit(cwd, ["push", "-u", "origin", branch]);
1364
- }
1365
- console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
1366
- return branch;
1367
- }
1368
-
1369
1548
  // src/commands/refine.ts
1370
1549
  import { readFileSync as readFileSync5 } from "fs";
1371
- import { join as join8 } from "path";
1550
+ import { join as join9 } from "path";
1372
1551
  async function runRefine(requirementId) {
1373
1552
  const cfg = await ensureLoggedConfig();
1374
- const filePath = join8(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
1553
+ const filePath = join9(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
1375
1554
  const content = readFileSync5(filePath, "utf8");
1376
1555
  const api = createApmApiClient(cfg);
1377
1556
  const data = await api.cliRequirements.refine({ requirementId, content });
@@ -1383,13 +1562,13 @@ import { spawnSync } from "child_process";
1383
1562
 
1384
1563
  // src/version.ts
1385
1564
  import { readFileSync as readFileSync6 } from "fs";
1386
- import { dirname as dirname2, join as join9 } from "path";
1565
+ import { dirname as dirname3, join as join10 } from "path";
1387
1566
  import { fileURLToPath as fileURLToPath2 } from "url";
1388
1567
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
1389
1568
  function readCliVersion() {
1390
1569
  try {
1391
- const dir = dirname2(fileURLToPath2(import.meta.url));
1392
- const pkgPath = join9(dir, "..", "package.json");
1570
+ const dir = dirname3(fileURLToPath2(import.meta.url));
1571
+ const pkgPath = join10(dir, "..", "package.json");
1393
1572
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
1394
1573
  return pkg.version ?? "0.0.0";
1395
1574
  } catch {
@@ -1458,8 +1637,8 @@ async function runUpdate() {
1458
1637
 
1459
1638
  // src/commands/update-skills.ts
1460
1639
  import { cpSync as cpSync2, existsSync as existsSync4, rmSync, statSync as statSync3 } from "fs";
1461
- import { join as join10 } from "path";
1462
- var TEMPLATE_SKILLS_DIR = join10(CLI_TEMPLATE_DIR, "skills");
1640
+ import { join as join11 } from "path";
1641
+ var TEMPLATE_SKILLS_DIR = join11(CLI_TEMPLATE_DIR, "skills");
1463
1642
  async function runUpdateSkills() {
1464
1643
  const apmDir = WORKSPACE_APM_DIR;
1465
1644
  if (!existsSync4(apmDir)) {
@@ -1479,7 +1658,7 @@ async function runUpdateSkills() {
1479
1658
  if (!templateStat.isDirectory()) {
1480
1659
  throw new Error(`[apm] \u5185\u7F6E\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_SKILLS_DIR}`);
1481
1660
  }
1482
- const skillsDir = join10(apmDir, "skills");
1661
+ const skillsDir = join11(apmDir, "skills");
1483
1662
  if (existsSync4(skillsDir)) {
1484
1663
  rmSync(skillsDir, { recursive: true, force: true });
1485
1664
  }
@@ -1488,6 +1667,77 @@ async function runUpdateSkills() {
1488
1667
  console.log("[apm] \u5DF2\u66F4\u65B0 .apm/skills");
1489
1668
  }
1490
1669
 
1670
+ // src/commands/update-theater-skills.ts
1671
+ import { cpSync as cpSync3, existsSync as existsSync5, rmSync as rmSync2, statSync as statSync4 } from "fs";
1672
+ import { join as join12 } from "path";
1673
+ var TEMPLATE_THEATER_SKILLS_DIR = join12(CLI_TEMPLATE_DIR, "theater-skills");
1674
+ async function runUpdateTheaterSkills() {
1675
+ const apmDir = WORKSPACE_APM_DIR;
1676
+ if (!existsSync5(apmDir)) {
1677
+ console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1678
+ process.exit(1);
1679
+ }
1680
+ const apmStat = statSync4(apmDir);
1681
+ if (!apmStat.isDirectory()) {
1682
+ throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1683
+ }
1684
+ let templateStat;
1685
+ try {
1686
+ templateStat = statSync4(TEMPLATE_THEATER_SKILLS_DIR);
1687
+ } catch {
1688
+ throw new Error(
1689
+ `[apm] \u5185\u7F6E\u5267\u573A\u6280\u80FD\u6A21\u677F\u4E0D\u5B58\u5728: ${TEMPLATE_THEATER_SKILLS_DIR}`
1690
+ );
1691
+ }
1692
+ if (!templateStat.isDirectory()) {
1693
+ throw new Error(
1694
+ `[apm] \u5185\u7F6E\u5267\u573A\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_THEATER_SKILLS_DIR}`
1695
+ );
1696
+ }
1697
+ const theaterSkillsDir = join12(apmDir, "theater-skills");
1698
+ if (existsSync5(theaterSkillsDir)) {
1699
+ rmSync2(theaterSkillsDir, { recursive: true, force: true });
1700
+ }
1701
+ await ensureDirExists(apmDir);
1702
+ cpSync3(TEMPLATE_THEATER_SKILLS_DIR, theaterSkillsDir, { recursive: true });
1703
+ console.log("[apm] \u5DF2\u66F4\u65B0 .apm/theater-skills");
1704
+ }
1705
+
1706
+ // src/commands/sync-session-document.ts
1707
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
1708
+ var DEFAULT_FILE = "prd.md";
1709
+ async function runSyncSessionDocument(sessionId, options) {
1710
+ const trimmedSessionId = sessionId.trim();
1711
+ if (!trimmedSessionId) {
1712
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1713
+ process.exit(1);
1714
+ }
1715
+ const fileArg = (options?.file?.trim() || DEFAULT_FILE).replace(/\\/g, "/");
1716
+ const apmRoot = options?.apmRoot ?? WORKSPACE_APM_DIR;
1717
+ const localRelativePath = theaterArtifactLocalRelativePath(fileArg);
1718
+ const absPath = theaterSessionDocumentPath(
1719
+ trimmedSessionId,
1720
+ fileArg,
1721
+ apmRoot
1722
+ );
1723
+ if (!existsSync6(absPath)) {
1724
+ console.error(`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
1725
+ \u8BF7\u5148\u521B\u5EFA\u8BE5\u6587\u4EF6\u540E\u518D\u540C\u6B65`);
1726
+ process.exit(1);
1727
+ }
1728
+ const content = readFileSync7(absPath, "utf8");
1729
+ const cfg = await ensureLoggedConfig();
1730
+ const api = createApmApiClient(cfg);
1731
+ const data = await api.theaterSessionArtifact.upsert({
1732
+ sessionId: trimmedSessionId,
1733
+ fileName: normalizeTheaterArtifactFileName(fileArg),
1734
+ content
1735
+ });
1736
+ console.log(
1737
+ `[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u6587\u6863: ${localRelativePath} \u2192 ${data.fileName} (artifactId=${data.id})`
1738
+ );
1739
+ }
1740
+
1491
1741
  // src/commands/update-dev-status.ts
1492
1742
  async function runUpdateDevStatus(requirementId, status) {
1493
1743
  const cfg = await ensureLoggedConfig();
@@ -1514,19 +1764,19 @@ async function runUpdateStatus(requirementId, status) {
1514
1764
  import path5 from "node:path";
1515
1765
 
1516
1766
  // src/commands/deploy/internal/apm-config.ts
1517
- import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
1767
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
1518
1768
  import { resolve as resolve4 } from "node:path";
1519
1769
  function loadApmConfig(options) {
1520
1770
  const p = resolve4(
1521
1771
  process.cwd(),
1522
1772
  options?.configPath ?? resolve4(WORKSPACE_APM_DIR, "apm.config.json")
1523
1773
  );
1524
- if (!existsSync5(p)) {
1774
+ if (!existsSync7(p)) {
1525
1775
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
1526
1776
  process.exit(1);
1527
1777
  }
1528
1778
  try {
1529
- const raw = readFileSync7(p, "utf8");
1779
+ const raw = readFileSync8(p, "utf8");
1530
1780
  return JSON.parse(raw);
1531
1781
  } catch (e) {
1532
1782
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -1625,7 +1875,7 @@ import path4 from "node:path";
1625
1875
  import Docker from "dockerode";
1626
1876
 
1627
1877
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
1628
- import { existsSync as existsSync6, readFileSync as readFileSync8 } from "node:fs";
1878
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
1629
1879
  import path from "node:path";
1630
1880
  function asOptionalTlsBuffer(value) {
1631
1881
  if (typeof value !== "string") {
@@ -1637,8 +1887,8 @@ function asOptionalTlsBuffer(value) {
1637
1887
  if (normalized === "") {
1638
1888
  return void 0;
1639
1889
  }
1640
- if (existsSync6(normalized)) {
1641
- return readFileSync8(normalized);
1890
+ if (existsSync8(normalized)) {
1891
+ return readFileSync9(normalized);
1642
1892
  }
1643
1893
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
1644
1894
  if (looksLikePath) {
@@ -1848,7 +2098,7 @@ var DockerodeClient = class {
1848
2098
  var createDockerodeClient = (config) => new DockerodeClient(config);
1849
2099
 
1850
2100
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
1851
- import { existsSync as existsSync7, readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
2101
+ import { existsSync as existsSync9, readFileSync as readFileSync10, statSync as statSync5 } from "node:fs";
1852
2102
  import path2 from "node:path";
1853
2103
  function stripSurroundingQuotes(value) {
1854
2104
  const t = value.trim();
@@ -1865,10 +2115,10 @@ function loadEnvFromFile(envFilePath) {
1865
2115
  return {};
1866
2116
  }
1867
2117
  const targetPath = path2.resolve(envFilePath);
1868
- if (!existsSync7(targetPath) || !statSync4(targetPath).isFile()) {
2118
+ if (!existsSync9(targetPath) || !statSync5(targetPath).isFile()) {
1869
2119
  return {};
1870
2120
  }
1871
- const raw = readFileSync9(targetPath, "utf-8");
2121
+ const raw = readFileSync10(targetPath, "utf-8");
1872
2122
  const result = {};
1873
2123
  for (const line of raw.split(/\r?\n/)) {
1874
2124
  const normalized = line.trim();
@@ -2039,12 +2289,12 @@ function dockerPushImage(params, cwd) {
2039
2289
  }
2040
2290
 
2041
2291
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
2042
- import { existsSync as existsSync8 } from "node:fs";
2292
+ import { existsSync as existsSync10 } from "node:fs";
2043
2293
  import path3 from "node:path";
2044
2294
  function resolveDockerBuildPaths(cwd) {
2045
2295
  const dockerfilePath = path3.join(cwd, "Dockerfile");
2046
2296
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
2047
- if (!existsSync8(dockerfilePath)) {
2297
+ if (!existsSync10(dockerfilePath)) {
2048
2298
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
2049
2299
  }
2050
2300
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -2173,16 +2423,16 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
2173
2423
  import path7 from "node:path";
2174
2424
 
2175
2425
  // src/commands/deploy/internal/load-apm-dotenv.ts
2176
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
2177
- import { join as join11 } from "node:path";
2426
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
2427
+ import { join as join13 } from "node:path";
2178
2428
  function loadApmDotEnvIfPresent() {
2179
- const p = join11(WORKSPACE_APM_DIR, ".env");
2180
- if (!existsSync9(p)) {
2429
+ const p = join13(WORKSPACE_APM_DIR, ".env");
2430
+ if (!existsSync11(p)) {
2181
2431
  return;
2182
2432
  }
2183
2433
  let text;
2184
2434
  try {
2185
- text = readFileSync10(p, "utf8");
2435
+ text = readFileSync11(p, "utf8");
2186
2436
  } catch {
2187
2437
  return;
2188
2438
  }
@@ -2207,14 +2457,14 @@ function loadApmDotEnvIfPresent() {
2207
2457
  }
2208
2458
 
2209
2459
  // src/commands/deploy/internal/minio.ts
2210
- import { statSync as statSync5 } from "node:fs";
2460
+ import { statSync as statSync6 } from "node:fs";
2211
2461
  import { readdir, readFile } from "node:fs/promises";
2212
2462
  import path6 from "node:path";
2213
2463
  import * as Minio from "minio";
2214
2464
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
2215
2465
  async function isDirectoryPath(dir) {
2216
2466
  try {
2217
- const st = statSync5(dir);
2467
+ const st = statSync6(dir);
2218
2468
  return st.isDirectory();
2219
2469
  } catch {
2220
2470
  return false;
@@ -2244,7 +2494,7 @@ async function collectFiles(root) {
2244
2494
  if (e.isDirectory()) {
2245
2495
  await walk(abs, rel);
2246
2496
  } else if (e.isFile()) {
2247
- const st = statSync5(abs);
2497
+ const st = statSync6(abs);
2248
2498
  out.push({
2249
2499
  absPath: abs,
2250
2500
  relativePath: rel.replace(/\\/g, "/"),
@@ -2566,6 +2816,25 @@ function buildProgram() {
2566
2816
  ).action(async () => {
2567
2817
  await runUpdateSkills();
2568
2818
  });
2819
+ program.command("update-theater-skills").description(
2820
+ "\u5220\u9664\u5DE5\u4F5C\u533A .apm/theater-skills \u540E\uFF0C\u4ECE\u5F53\u524D CLI \u5185\u7F6E\u6A21\u677F\u91CD\u65B0\u590D\u5236\u5267\u573A\u6280\u80FD\u76EE\u5F55"
2821
+ ).action(async () => {
2822
+ await runUpdateTheaterSkills();
2823
+ });
2824
+ program.command("pull-session").description(
2825
+ "\u5C06\u5E73\u53F0\u5267\u573A\u4F1A\u8BDD\u4EA7\u7269\u6587\u6863\u4E0B\u8F7D\u5230 .apm/theater-sessions/<sessionId>/\uFF08\u5982 docs/prd.md\uFF09\uFF1B\u672C\u5730\u5DF2\u6709\u540C\u540D\u6587\u4EF6\u65F6\u8986\u76D6"
2826
+ ).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").action(async (sessionId) => {
2827
+ const dir = await runPullTheaterSession(sessionId);
2828
+ console.log(`[apm] \u4F1A\u8BDD\u6587\u6863\u76EE\u5F55: ${dir}`);
2829
+ });
2830
+ program.command("sync-session-document").description(
2831
+ "\u5C06 .apm/theater-sessions/<sessionId>/docs/ \u4E0B Markdown \u6587\u6863 upsert \u5230\u5E73\u53F0\u4F1A\u8BDD\u4EA7\u7269\uFF08\u4E0D\u542B\u5BF9\u8BDD\u65E5\u5FD7\uFF09"
2832
+ ).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").option(
2833
+ "--file <path>",
2834
+ "\u6587\u6863\u540D\u6216\u76F8\u5BF9\u8DEF\u5F84\uFF08\u5982 prd.md\u3001docs/backend.md\uFF1B\u672C\u5730\u56FA\u5B9A\u843D\u5728 docs/ \u4E0B\uFF09"
2835
+ ).action(async (sessionId, opts) => {
2836
+ await runSyncSessionDocument(sessionId, { file: opts.file });
2837
+ });
2569
2838
  program.command("pull").description(
2570
2839
  "GET /api/cli/requirements/pull\uFF0C\u540C\u6B65\u6570\u636E\u4E0E\u9644\u4EF6\u5230 .apm/workitems/<\u9700\u6C42ID>"
2571
2840
  ).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {