ai-project-manage-cli 4.0.22 → 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
@@ -177,6 +177,10 @@ var requestConfig = {
177
177
  })
178
178
  },
179
179
  theater: {
180
+ sessionBranchBaseline: defineEndpoint({
181
+ method: "GET",
182
+ path: "/theater/sessions/branch-baseline"
183
+ }),
180
184
  reportMemberAgentProgress: defineEndpoint({
181
185
  method: "POST",
182
186
  path: "/theater/sessions/report-member-agent-progress"
@@ -228,9 +232,16 @@ function requirementWorkitemsDir(requirementId, workspaceDir = WORKSPACE_APM_DIR
228
232
  function theaterSessionDir(sessionId, apmRoot = WORKSPACE_APM_DIR) {
229
233
  return join2(apmRoot, "theater-sessions", sessionId);
230
234
  }
231
- function theaterSessionDocumentPath(sessionId, relativePath = "docs/prd.md", apmRoot = WORKSPACE_APM_DIR) {
232
- const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
233
- return join2(theaterSessionDir(sessionId, apmRoot), normalized);
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);
234
245
  }
235
246
  async function ensureLoggedConfig() {
236
247
  const cfg = await ensureApmConfig();
@@ -813,7 +824,7 @@ async function runPull(requirementId, workspaceDir) {
813
824
  import { writeFileSync as writeFileSync4 } from "fs";
814
825
  import { dirname as dirname2, join as join6 } from "path";
815
826
  function normalizeRelativePath(fileName) {
816
- return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "");
827
+ return theaterArtifactLocalRelativePath(fileName);
817
828
  }
818
829
  async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
819
830
  const trimmedSessionId = sessionId.trim();
@@ -824,6 +835,7 @@ async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
824
835
  const api = createApmApiClient(cfg);
825
836
  const sessionDir = theaterSessionDir(trimmedSessionId, apmRoot);
826
837
  await ensureDirExists(sessionDir);
838
+ await ensureDirExists(join6(sessionDir, "docs"));
827
839
  const pageSize = 500;
828
840
  let page = 1;
829
841
  const items = [];
@@ -851,6 +863,200 @@ async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
851
863
  return sessionDir;
852
864
  }
853
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
+
854
1060
  // src/theater-job-report.ts
855
1061
  import { randomUUID } from "crypto";
856
1062
  function extractErrorMessage(err) {
@@ -1074,6 +1280,20 @@ async function handleTheaterJob(api, ws, payload) {
1074
1280
  });
1075
1281
  return;
1076
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
+ }
1077
1297
  try {
1078
1298
  await runPullTheaterSession(sessionId, apmRoot);
1079
1299
  } catch (pullErr) {
@@ -1093,6 +1313,23 @@ async function handleTheaterJob(api, ws, payload) {
1093
1313
  const agent = await createAgentForJob(payload);
1094
1314
  const runResult = await runAgentStream(payload, eventSession, agent);
1095
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
+ }
1096
1333
  const assistantText = eventSession.getAssistantText();
1097
1334
  const content = runResult.failed ? runResult.failReason || assistantText || "\u672A\u77E5\u9519\u8BEF" : assistantText || "[Agent \u672A\u4EA7\u751F\u6587\u672C\u8F93\u51FA]";
1098
1335
  await submitTheaterResult(api, ws, {
@@ -1308,150 +1545,6 @@ async function runLogin(opts) {
1308
1545
  console.log(JSON.stringify({ userId: cfg.userId, baseUrl: cfg.baseUrl }, null, 2));
1309
1546
  }
1310
1547
 
1311
- // src/commands/branch.ts
1312
- import { execFile } from "child_process";
1313
- import { resolve as resolve3 } from "path";
1314
- import { promisify } from "util";
1315
- var execFileAsync = promisify(execFile);
1316
- async function fetchBaselineBranchFromApi(requirementId, cwd) {
1317
- const cfg = await ensureLoggedConfig();
1318
- const api = createApmApiClient(cfg);
1319
- const workdirPath = resolve3(cwd);
1320
- const res = await api.cliRequirements.branchBaseline({
1321
- requirementId,
1322
- workdirPath
1323
- });
1324
- if (!res.repositoryId) {
1325
- throw new Error(
1326
- `[apm] \u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}
1327
- \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`
1328
- );
1329
- }
1330
- const name = (res.defaultBranch ?? "").trim();
1331
- if (!name) {
1332
- throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
1333
- }
1334
- return name;
1335
- }
1336
- function branchNameForRequirement(requirementId) {
1337
- const id = requirementId.trim();
1338
- if (!id) {
1339
- throw new Error("[apm] \u9700\u6C42 ID \u4E0D\u80FD\u4E3A\u7A7A");
1340
- }
1341
- if (/[\s/\\]/.test(id)) {
1342
- throw new Error(
1343
- "[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"
1344
- );
1345
- }
1346
- return `feat/req-${id}`;
1347
- }
1348
- async function execGit(cwd, args, quiet) {
1349
- try {
1350
- const { stdout, stderr } = await execFileAsync("git", args, {
1351
- cwd,
1352
- encoding: "utf8",
1353
- maxBuffer: 10 * 1024 * 1024
1354
- });
1355
- if (!quiet && stderr.trim()) {
1356
- process.stderr.write(stderr);
1357
- }
1358
- return stdout;
1359
- } catch (err) {
1360
- const e = err;
1361
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
1362
- throw new Error(
1363
- `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
1364
- );
1365
- }
1366
- }
1367
- async function ensureGitRepo(cwd) {
1368
- await execGit(cwd, ["rev-parse", "--git-dir"], true);
1369
- }
1370
- async function getCurrentBranch(cwd) {
1371
- const name = (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1372
- return name;
1373
- }
1374
- async function isWorkingTreeDirty(cwd) {
1375
- const out = await execGit(cwd, ["status", "--porcelain"], true);
1376
- return out.trim().length > 0;
1377
- }
1378
- async function remoteHeadBranchExists(cwd, branch) {
1379
- const out = await execGit(
1380
- cwd,
1381
- ["ls-remote", "--heads", "origin", branch],
1382
- true
1383
- );
1384
- return out.trim().length > 0;
1385
- }
1386
- async function localBranchExists(cwd, branch) {
1387
- try {
1388
- await execGit(
1389
- cwd,
1390
- ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
1391
- true
1392
- );
1393
- return true;
1394
- } catch {
1395
- return false;
1396
- }
1397
- }
1398
- async function runBranch(requirementId, options = {}) {
1399
- const cwd = options.cwd ?? process.cwd();
1400
- const branch = branchNameForRequirement(requirementId);
1401
- const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
1402
- await ensureGitRepo(cwd);
1403
- const current = await getCurrentBranch(cwd);
1404
- const dirty = await isWorkingTreeDirty(cwd);
1405
- if (dirty) {
1406
- if (current === branch) {
1407
- await execGit(cwd, ["add", "-A"]);
1408
- await execGit(cwd, ["commit", "-m", commitMessage]);
1409
- } else {
1410
- await execGit(cwd, [
1411
- "stash",
1412
- "push",
1413
- "-u",
1414
- "-m",
1415
- `apm: switch to ${branch}`
1416
- ]);
1417
- }
1418
- }
1419
- const remoteExists = await remoteHeadBranchExists(cwd, branch);
1420
- if (remoteExists) {
1421
- await execGit(cwd, ["fetch", "origin", branch]);
1422
- const hasLocal = await localBranchExists(cwd, branch);
1423
- if (hasLocal) {
1424
- await execGit(cwd, ["checkout", branch]);
1425
- await execGit(cwd, ["pull", "--no-edit"]);
1426
- } else {
1427
- await execGit(cwd, ["checkout", "-b", branch, `origin/${branch}`]);
1428
- }
1429
- } else {
1430
- const onBranch = await getCurrentBranch(cwd) === branch;
1431
- if (!onBranch) {
1432
- const hasLocal = await localBranchExists(cwd, branch);
1433
- if (hasLocal) {
1434
- await execGit(cwd, ["checkout", branch]);
1435
- } else {
1436
- const baselineBranch = await fetchBaselineBranchFromApi(
1437
- requirementId,
1438
- cwd
1439
- );
1440
- await execGit(cwd, ["fetch", "origin", baselineBranch]);
1441
- await execGit(cwd, [
1442
- "checkout",
1443
- "-b",
1444
- branch,
1445
- `origin/${baselineBranch}`
1446
- ]);
1447
- }
1448
- }
1449
- await execGit(cwd, ["push", "-u", "origin", branch]);
1450
- }
1451
- console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
1452
- return branch;
1453
- }
1454
-
1455
1548
  // src/commands/refine.ts
1456
1549
  import { readFileSync as readFileSync5 } from "fs";
1457
1550
  import { join as join9 } from "path";
@@ -1612,21 +1705,19 @@ async function runUpdateTheaterSkills() {
1612
1705
 
1613
1706
  // src/commands/sync-session-document.ts
1614
1707
  import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
1615
- var DEFAULT_RELATIVE_PATH = "docs/prd.md";
1708
+ var DEFAULT_FILE = "prd.md";
1616
1709
  async function runSyncSessionDocument(sessionId, options) {
1617
1710
  const trimmedSessionId = sessionId.trim();
1618
1711
  if (!trimmedSessionId) {
1619
1712
  console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1620
1713
  process.exit(1);
1621
1714
  }
1622
- const relativePath = (options?.file?.trim() || DEFAULT_RELATIVE_PATH).replace(
1623
- /\\/g,
1624
- "/"
1625
- );
1715
+ const fileArg = (options?.file?.trim() || DEFAULT_FILE).replace(/\\/g, "/");
1626
1716
  const apmRoot = options?.apmRoot ?? WORKSPACE_APM_DIR;
1717
+ const localRelativePath = theaterArtifactLocalRelativePath(fileArg);
1627
1718
  const absPath = theaterSessionDocumentPath(
1628
1719
  trimmedSessionId,
1629
- relativePath,
1720
+ fileArg,
1630
1721
  apmRoot
1631
1722
  );
1632
1723
  if (!existsSync6(absPath)) {
@@ -1639,10 +1730,12 @@ async function runSyncSessionDocument(sessionId, options) {
1639
1730
  const api = createApmApiClient(cfg);
1640
1731
  const data = await api.theaterSessionArtifact.upsert({
1641
1732
  sessionId: trimmedSessionId,
1642
- fileName: relativePath,
1733
+ fileName: normalizeTheaterArtifactFileName(fileArg),
1643
1734
  content
1644
1735
  });
1645
- console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u6587\u6863: ${relativePath} (artifactId=${data.id})`);
1736
+ console.log(
1737
+ `[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u6587\u6863: ${localRelativePath} \u2192 ${data.fileName} (artifactId=${data.id})`
1738
+ );
1646
1739
  }
1647
1740
 
1648
1741
  // src/commands/update-dev-status.ts
@@ -2738,7 +2831,7 @@ function buildProgram() {
2738
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"
2739
2832
  ).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").option(
2740
2833
  "--file <path>",
2741
- "\u76F8\u5BF9\u4F1A\u8BDD\u76EE\u5F55\u7684\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 docs/prd.md\uFF1B\u540E\u7AEF\u5E38\u7528 docs/backend.md\u3001docs/api.md\uFF09"
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"
2742
2835
  ).action(async (sessionId, opts) => {
2743
2836
  await runSyncSessionDocument(sessionId, { file: opts.file });
2744
2837
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "4.0.22",
3
+ "version": "4.0.23",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,13 +1,19 @@
1
1
  ---
2
2
  name: apm-theater-dev
3
- description: 按剧场会话 ID 全自动编码:切分支、读会话 PRD(及 backend/api 文档)、按改动规模选择 Quick 或 Spec、提交并推送、同步 docs 产物;子 Agent 承担编码与规划落地;当用户 @ 本技能、提及剧场全自动开发或「apm-theater-dev」时使用。
3
+ description: 按剧场会话 ID 全自动编码:按需读会话 PRD(及 backend/api 文档)、按改动规模选择 Quick 或 Spec、推送代码;子 Agent 承担编码与规划落地;当用户 @ 本技能、提及剧场全自动开发或「apm-theater-dev」时使用。
4
4
  ---
5
5
 
6
6
  # 剧场全自动开发(按会话 ID)
7
7
 
8
8
  用户给出 **剧场会话 ID**(`sessionId`,与 `.apm/theater-sessions/<sessionId>/` 目录名一致)。**缺 ID 时索要,不猜测。**
9
9
 
10
- `THEATER_JOB` 开始时会自动 pull 平台文档到本地。Agent **对话日志不上传**;`docs/` 等业务文档在变更后须 `sync-session-document`。
10
+ 由 **`THEATER_JOB` / `apm connect`** 自动完成(**无需在本技能中切分支**):
11
+
12
+ - 检出或创建 **`feat/req-<sessionId>`** 并拉取最新
13
+ - **pull** 平台会话文档到 `.apm/theater-sessions/<sessionId>/docs/`
14
+ - Agent 结束后若有未提交变更,CLI 自动 **`git add` & `commit`**
15
+
16
+ Agent **对话日志不上传**。**不要**执行 `apm sync-session-document`(会话文档由网页或其他角色维护)。
11
17
 
12
18
  ---
13
19
 
@@ -19,60 +25,47 @@ description: 按剧场会话 ID 全自动编码:切分支、读会话 PRD(
19
25
  | **会话上下文** | 若由 `THEATER_JOB` 触发,`theaterSessionId` 即 `sessionId`;缺省时向用户索要,不猜测。 |
20
26
 
21
27
  **会话根目录**:`.apm/theater-sessions/<sessionId>/`
22
- **PRD**:`.apm/theater-sessions/<sessionId>/docs/prd.md`
23
- **可选参考**:`docs/backend.md`、`docs/api.md`
28
+ **PRD(按需)**:`docs/prd.md`
29
+ **可选参考(按需)**:`docs/backend.md`、`docs/api.md`
24
30
 
25
31
  ---
26
32
 
27
33
  ## 技能文件定位(apm-propose / apm-apply-change)
28
34
 
29
- Spec 路径依赖的技能**仅**来自 **`.apm/skills/`**。执行前父 Agent **须 Read**:
35
+ Spec 路径依赖的技能**仅**来自 **`.apm/skills/`**。进入 Spec 子流程前父 Agent **须 Read**:
30
36
 
31
37
  | 技能 | 路径 |
32
38
  | -------------------- | --------------------------------------- |
33
39
  | **apm-propose** | `.apm/skills/apm-propose/SKILL.md` |
34
40
  | **apm-apply-change** | `.apm/skills/apm-apply-change/SKILL.md` |
35
41
 
36
- instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-instruction.md`)。若上述 `SKILL.md` **不存在或不可读**:**不得**进入 Spec 子流程;在表格中标记失败原因(例如需先 `apm init` 或 `apm update-skills`),并停止步骤 4
42
+ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-instruction.md`)。若上述 `SKILL.md` **不存在或不可读**:**不得**进入 Spec 子流程;在表格中标记失败原因(例如需先 `apm init` 或 `apm update-skills`),并停止步骤 3
37
43
 
38
- 委派子 Agent 时须说明:本轮为**剧场会话**,**`sessionId`** 替代需求 ID;PRD 在 **`docs/prd.md`**;Spec 工件落在 **`.apm/theater-sessions/<sessionId>/`**(非 `workitems`)。
44
+ 委派子 Agent 时须说明:本轮为**剧场会话**,**`sessionId`** 替代需求 ID;PRD 在 **`docs/prd.md`**(按需阅读);Spec 工件落在 **`.apm/theater-sessions/<sessionId>/`**(非 `workitems`)。
39
45
 
40
46
  ---
41
47
 
42
48
  ## 流程总览
43
49
 
44
- | 序号 | 步骤 | 说明 |
45
- | ---- | --------------------- | ---------------------------------------------------------------------------------------- |
46
- | 1 | **切分支** | 检出或创建 `feat/theater-<sessionId>`(git,无 `apm branch` 剧场命令) |
47
- | 2 | **读 PRD + 成本评估** | **Read** `docs/prd.md`(及需要的 `backend.md` / `api.md`),判定 Quick / Spec |
48
- | 3 | **Quick 开发** | 仅当判定为「改动成本较小」时执行;由 **子 Agent** 写代码 |
49
- | 4 | **Spec 开发** | 仅当判定为「改动成本较大」时执行;先 **apm-propose** **apm-apply-change**(子 Agent |
50
- | 5 | **提交与推送** | `git` 提交并 `push`,工作区干净 |
51
- | 6 | **同步会话文档** | 对本轮有改动的 `.md` 逐一 `apm sync-session-document`(仅 `docs/` 等业务文档,不含日志) |
52
- | 7 | **对用户回复** | **一张 Markdown 表格**汇总各步执行结果(见文末模板) |
53
-
54
- ---
55
-
56
- ## 步骤 1:切分支
57
-
58
- 1. 在**仓库/工作区根目录**执行(分支名固定为 `feat/theater-<sessionId>`):
59
-
60
- ```bash
61
- git fetch origin
62
- git switch feat/theater-<sessionId> 2>/dev/null || git switch -c feat/theater-<sessionId>
63
- ```
50
+ | 序号 | 步骤 | 说明 |
51
+ | ---- | ------------------------- | ----------------------------------------------------------------------------------------- |
52
+ | 1 | **按需读文档 + 成本评估** | 按本轮任务**按需 Read** `docs/prd.md` 及需要的 `backend.md` / `api.md`,判定 Quick / Spec |
53
+ | 2 | **Quick 开发** | 仅当判定为「改动成本较小」时执行;由 **子 Agent** 写代码 |
54
+ | 3 | **Spec 开发** | 仅当判定为「改动成本较大」时执行;先 **apm-propose** **apm-apply-change**(子 Agent) |
55
+ | 4 | **推送** | `git push`;确认工作区干净(提交通常已由 CLI Agent 结束时完成) |
64
56
 
65
- 若远端已有该分支:`git switch feat/theater-<sessionId>` `git pull --no-edit`(按需)。
66
-
67
- 2. 记录:是否成功、当前分支名、简要输出或错误(写入最终表格)。**失败则按「Guardrails → 失败即终止」处理**。
57
+ 流程**仅以上 4 步**,**无**同步会话文档环节(不执行 `apm sync-session-document`)。
68
58
 
69
59
  ---
70
60
 
71
- ## 步骤 2:读取 PRD 并评估改动成本
61
+ ## 步骤 1:按需读文档并评估改动成本
72
62
 
73
- 1. **Read** 全文:`.apm/theater-sessions/<sessionId>/docs/prd.md`。
74
- 2. 若实现涉及后端/接口,按需 **Read** `docs/backend.md`、`docs/api.md`。
75
- 3. `prd.md` 读失败则**停止后续实现**,仅在表格中标记失败原因。
63
+ 1. **不要**无差别全文 Read `prd.md`;根据主持委派任务、已有对话与实现范围,**按需**阅读:
64
+ - 需要理解需求范围或验收时 **Read** `docs/prd.md`(全文或相关章节)
65
+ - 涉及后端实现思路 → **Read** `docs/backend.md`
66
+ - 涉及接口联调 → **Read** `docs/api.md`
67
+ 2. 若任务明确只需改代码、且上下文已足够,可**跳过**读 PRD,但在表格中注明依据(例如「主持任务已说明改 X 文件」)。
68
+ 3. 若判定需要 PRD/backend/api 但文件不存在或读失败,**停止后续实现**,仅在表格中标记失败原因。
76
69
  4. **不**为评估向用户发起追问;信息不足时倾向 **Spec**(保守)。
77
70
 
78
71
  ### Quick(较小)与 Spec(较大)判定参考
@@ -81,85 +74,67 @@ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-i
81
74
 
82
75
  - 影响范围局部:少量文件或单一层次(例如仅前端组件、或仅一个后端模块小改)。
83
76
  - 无新表结构/大规模迁移/权限模型变更。
84
- - PRD 验收点清晰且数量少(经验上 **≤3** 条独立验收维度)。
77
+ - 验收点清晰且数量少(经验上 **≤3** 条独立验收维度)。
85
78
  - 不需要跨多服务的架构裁定即可开工。
86
79
 
87
80
  **倾向 Spec**(命中任一条即可):
88
81
 
89
82
  - 前后端联动、多包改造或新公共抽象。
90
83
  - 新数据模型、迁移、或安全/审计/权限相关。
91
- - PRD 范围大、条款多,或存在明显「待确认/多方案」需先规划。
84
+ - 范围大、条款多,或存在明显「待确认/多方案」需先规划。
92
85
  - 评估认为不先产出 **proposal / design / specs / tasks** 则难以保证实现与验收对齐。
93
86
 
94
- 在表格「步骤 2」中写明结论:**Quick** 或 **Spec**,以及**一行内**理由(关键词即可)。
87
+ 在表格「步骤 1」中写明结论:**Quick** 或 **Spec**,以及**一行内**理由(关键词即可);若读了哪些文档也一并注明。
95
88
 
96
89
  ---
97
90
 
98
- ## 步骤 3:Quick 开发模式(子 Agent)
91
+ ## 步骤 2:Quick 开发模式(子 Agent)
99
92
 
100
- **条件**:步骤 2 判定为 **Quick**。
93
+ **条件**:步骤 1 判定为 **Quick**。
101
94
 
102
- 1. 父 Agent 已通过 **Read** 掌握 `prd.md`(及需要的 backend/api);委派时在提示中写明 **`sessionId`**、会话根路径、以及「实现须严格对照 PRD,改动范围最小化」。
95
+ 1. 父 Agent 已按需掌握必要文档;委派时在提示中写明 **`sessionId`**、会话根路径、以及「实现须对照已读文档与主持任务,改动范围最小化」。
103
96
  2. 使用 **Task** 工具,`subagent_type: generalPurpose`,**readonly: false**,委派子 Agent:
104
- - 自行 **Read** `.apm/theater-sessions/<sessionId>/docs/prd.md`(及需要的 `backend.md` / `api.md`)。
105
- - 按 PRD 直接改代码;遵守本仓库构建与依赖约定(AGENTS.md)。
97
+ - **按需 Read** `docs/prd.md` 及需要的 `backend.md` / `api.md`。
98
+ - 按任务与文档直接改代码;遵守本仓库构建与依赖约定(AGENTS.md)。
106
99
  - 完成后在返回中说明:改了哪些路径、如何对照验收、是否通过本地可执行的检查(若跑了 `rushx build` / 测试等则写明结果)。
107
- 3. 父 Agent 根据子 Agent 返回在表格中填写步骤 3 **状态**;本模式下步骤 4 填 **跳过**。
100
+ 3. 父 Agent 根据子 Agent 返回在表格中填写步骤 2 **状态**;本模式下步骤 3 填 **跳过**。
108
101
 
109
102
  ---
110
103
 
111
- ## 步骤 4:Spec 开发模式(子 Agent)
104
+ ## 步骤 3:Spec 开发模式(子 Agent)
112
105
 
113
- **条件**:步骤 2 判定为 **Spec**。
106
+ **条件**:步骤 1 判定为 **Spec**。
114
107
 
115
108
  1. 父 Agent **Read** **apm-propose**、**apm-apply-change** 的 `SKILL.md`(**仅** `.apm/skills/` 下路径,见上节)。
116
- 2. **子 Agent A(规划)**:Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-propose/SKILL.md` 并遵循,但将工件目录改为 **`.apm/theater-sessions/<sessionId>/`**,PRD 来源为 **`docs/prd.md`**,上下文使用 **`sessionId`** 而非 `requirementId`;生成 **proposal、design、specs、tasks** 等(顺序以 SKILL 为准)。
109
+ 2. **子 Agent A(规划)**:Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-propose/SKILL.md` 并遵循,但将工件目录改为 **`.apm/theater-sessions/<sessionId>/`**,PRD 来源为 **`docs/prd.md`**(按需阅读),上下文使用 **`sessionId`** 而非 `requirementId`;生成 **proposal、design、specs、tasks** 等(顺序以 SKILL 为准)。
117
110
  3. **子 Agent B(实现)**:待 A 成功落盘后,再 Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-apply-change/SKILL.md` 并遵循:按 **`.apm/theater-sessions/<sessionId>/tasks.md`** 驱动实现与勾选。
118
111
  4. 若未产出可用 **`tasks.md`**,不得强行进入 **apm-apply-change**;表格中标记阻塞原因。
119
- 5. 表格中步骤 3 填 **跳过**;步骤 4 分两行或合并一行写清 propose / apply 状态。
112
+ 5. 表格中步骤 2 填 **跳过**;步骤 3 分两行或合并一行写清 propose / apply 状态。
120
113
 
121
114
  ---
122
115
 
123
- ## 步骤 5:提交并推送
116
+ ## 步骤 4:推送
124
117
 
125
118
  在仓库根目录执行:
126
119
 
127
- 1. `git status`:确认变更范围。
128
- 2. 若有未提交变更:`git add -A`,然后 `git commit -m "feat(theater-<sessionId>): <简短说明>"`(说明应概括本轮实现;若子 Agent 已多次 commit,以 `status` 为准)。
129
- 3. `git push`:推送到当前分支对应远程(首次必要时 `-u origin feat/theater-<sessionId>`)。
130
- 4. 再次 `git status`:**须为干净工作区**。
131
-
132
- 将命令结果、最终分支名、是否已 push、工作区是否干净写入表格。
133
-
134
- ---
135
-
136
- ## 步骤 6:同步会话文档
137
-
138
- 对本轮**有内容变更**的 Markdown(通常在 `docs/`,Spec 模式可能含会话根目录下的 `proposal.md` 等),在仓库根目录逐一执行:
139
-
140
- ```bash
141
- apm sync-session-document <sessionId> --file docs/prd.md
142
- apm sync-session-document <sessionId> --file <其他相对路径>
143
- ```
120
+ 1. `git status`:确认变更范围;若仍有未提交变更(例如子 Agent 在 CLI 自动 commit 之后又改了文件),执行 `git add -A` 与 `git commit -m "feat(req-<sessionId>): <简短说明>"`。
121
+ 2. `git push`:推送到当前分支 **`feat/req-<sessionId>`**(首次必要时 `-u origin feat/req-<sessionId>`)。
122
+ 3. 再次 `git status`:**须为干净工作区**。
144
123
 
145
- - **仅同步业务文档**;**不要**同步 Agent 对话日志(`sessions/` 等)。
146
- - 若本轮未改动任何需上平台的文档,表格步骤 6 填 **跳过**。
147
- - 同步失败时如实记录,不假装成功。
124
+ 将命令结果、当前分支名、是否已 push、工作区是否干净写入表格(见下节「回复格式」)。
148
125
 
149
126
  ---
150
127
 
151
- ## 步骤 7:对用户回复(表格)
128
+ ## 回复格式
152
129
 
153
- 对用户回复 **必须包含一张 Markdown 表格**,汇总 **步骤 1 ~ 6**。表头建议:
130
+ 完成步骤 4 后,对用户回复 **必须包含一张 Markdown 表格**,汇总 **步骤 1 ~ 4**:
154
131
 
155
132
  | 步骤 | 内容 | 结果 |
156
133
  | ---- | ------------------------------------------------ | ------------------------------ |
157
- | 1 | 切分支 `feat/theater-<sessionId>` | 成功 / 失败(原因) |
158
- | 2 | PRD + 成本评估 | Quick Spec;一行理由 / 失败 |
159
- | 3 | Quick 开发(子 Agent) | 成功 / 失败 / **跳过** |
160
- | 4 | Spec:apm-propose → apm-apply-change(子 Agent) | 成功 / 失败 / **跳过** |
161
- | 5 | commit & push;工作区干净 | 成功 / 失败(原因) |
162
- | 6 | `sync-session-document`(有改动的文档) | 成功 / 失败 / **跳过** |
134
+ | 1 | 按需读文档 + 成本评估 | Quick 或 Spec;读了哪些 / 失败 |
135
+ | 2 | Quick 开发(子 Agent) | 成功 / 失败 / **跳过** |
136
+ | 3 | Spec:apm-propose apm-apply-change(子 Agent) | 成功 / 失败 / **跳过** |
137
+ | 4 | push;工作区干净 | 成功 / 失败(原因) |
163
138
 
164
139
  **可选**:表格外增加**简短**摘要(当前分支、阻塞点)。
165
140
 
@@ -167,8 +142,8 @@ apm sync-session-document <sessionId> --file <其他相对路径>
167
142
 
168
143
  ## Guardrails
169
144
 
170
- - **失败即终止**:在用户提供的 **`sessionId`** 等参数合法、命令与路径按本技能书写的前提下,任一步骤(含切分支、读文件、子 Agent、`git`、同步文档)**一旦失败**:**停止后续所有步骤**,仅在表格中记录失败步骤与原因;**不要**为登录、依赖、网络等做**多次**重试。
145
+ - **失败即终止**:在用户提供的 **`sessionId`** 等参数合法、命令与路径按本技能书写的前提下,任一步骤(含读文件、子 Agent、`git`)**一旦失败**:**停止后续所有步骤**,仅在表格中记录失败步骤与原因;**不要**为登录、依赖、网络等做**多次**重试。
171
146
  - **例外(Agent 自身失误)**:若失败明显由执行 Agent **用错工作目录、读错/漏写路径** 等导致,**允许**纠正后**仅对该失败步骤再执行一次**;纠正后仍失败则**立即终止**。
172
- - **不要**在无 `prd.md` 的情况下编造需求实现。
147
+ - **不要**在无依据的情况下编造需求实现;需要 PRD 时须先 Read,读不到则停止。
173
148
  - **不要**上传或同步 Agent 对话日志。
174
149
  - **子 Agent** 提示中须带 **`sessionId`** 与会话根路径,避免改错工作树。