ai-project-manage-cli 4.0.21 → 4.0.22

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,6 +154,28 @@ 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: {
158
180
  reportMemberAgentProgress: defineEndpoint({
159
181
  method: "POST",
@@ -203,6 +225,13 @@ var WORKSPACE_APM_DIR = resolve(process.cwd(), ".apm");
203
225
  function requirementWorkitemsDir(requirementId, workspaceDir = WORKSPACE_APM_DIR) {
204
226
  return join2(workspaceDir, "workitems", requirementId);
205
227
  }
228
+ function theaterSessionDir(sessionId, apmRoot = WORKSPACE_APM_DIR) {
229
+ return join2(apmRoot, "theater-sessions", sessionId);
230
+ }
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);
234
+ }
206
235
  async function ensureLoggedConfig() {
207
236
  const cfg = await ensureApmConfig();
208
237
  if (!cfg.token) {
@@ -377,7 +406,7 @@ async function runComment(requirementId, file, options) {
377
406
  import { randomUUID as randomUUID2 } from "crypto";
378
407
  import WebSocket from "ws";
379
408
  import { Agent } from "@cursor/sdk";
380
- import { join as join6 } from "path";
409
+ import { join as join7 } from "path";
381
410
 
382
411
  // src/session-utils.ts
383
412
  import { appendFileSync } from "fs";
@@ -780,6 +809,48 @@ async function runPull(requirementId, workspaceDir) {
780
809
  return WORKITEMS_DIR;
781
810
  }
782
811
 
812
+ // src/commands/pull-theater-session.ts
813
+ import { writeFileSync as writeFileSync4 } from "fs";
814
+ import { dirname as dirname2, join as join6 } from "path";
815
+ function normalizeRelativePath(fileName) {
816
+ return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "");
817
+ }
818
+ async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
819
+ const trimmedSessionId = sessionId.trim();
820
+ if (!trimmedSessionId) {
821
+ throw new Error("sessionId \u4E0D\u80FD\u4E3A\u7A7A");
822
+ }
823
+ const cfg = await ensureLoggedConfig();
824
+ const api = createApmApiClient(cfg);
825
+ const sessionDir = theaterSessionDir(trimmedSessionId, apmRoot);
826
+ await ensureDirExists(sessionDir);
827
+ const pageSize = 500;
828
+ let page = 1;
829
+ const items = [];
830
+ while (true) {
831
+ const batch = await api.theaterSessionArtifact.list({
832
+ sessionId: trimmedSessionId,
833
+ page,
834
+ pageSize
835
+ });
836
+ items.push(...batch.items);
837
+ if (batch.total === 0 || items.length >= batch.total) break;
838
+ page += 1;
839
+ }
840
+ for (const item of items) {
841
+ const art = await api.theaterSessionArtifact.get({
842
+ sessionId: trimmedSessionId,
843
+ artifactId: item.id
844
+ });
845
+ const relativePath = normalizeRelativePath(art.fileName);
846
+ const destPath = join6(sessionDir, relativePath);
847
+ await ensureDirExists(dirname2(destPath));
848
+ writeFileSync4(destPath, art.content, "utf8");
849
+ console.log(`[apm] \u5DF2\u8986\u76D6\u4F1A\u8BDD\u6587\u6863: ${relativePath}`);
850
+ }
851
+ return sessionDir;
852
+ }
853
+
783
854
  // src/theater-job-report.ts
784
855
  import { randomUUID } from "crypto";
785
856
  function extractErrorMessage(err) {
@@ -987,6 +1058,7 @@ async function handleTheaterJob(api, ws, payload) {
987
1058
  throw new Error("THEATER_JOB \u7F3A\u5C11 memberKey");
988
1059
  }
989
1060
  console.log("[apm] \u5267\u573A\u4F1A\u8BDD:", sessionId, "\u6210\u5458:", memberKey);
1061
+ const apmRoot = join7(payload.cwd, ".apm");
990
1062
  let agentId = payload.agentId?.trim() || void 0;
991
1063
  try {
992
1064
  await reportTheaterProgress(api, ws, { sessionId, memberKey, logId });
@@ -1002,6 +1074,20 @@ async function handleTheaterJob(api, ws, payload) {
1002
1074
  });
1003
1075
  return;
1004
1076
  }
1077
+ try {
1078
+ await runPullTheaterSession(sessionId, apmRoot);
1079
+ } catch (pullErr) {
1080
+ logTheaterJobError("\u62C9\u53D6\u4F1A\u8BDD\u6587\u6863", pullErr, { sessionId, memberKey });
1081
+ await submitTheaterFailure(api, ws, {
1082
+ sessionId,
1083
+ memberKey,
1084
+ logId,
1085
+ agentId,
1086
+ stage: "\u62C9\u53D6\u4F1A\u8BDD\u6587\u6863",
1087
+ error: extractErrorMessage(pullErr)
1088
+ });
1089
+ return;
1090
+ }
1005
1091
  try {
1006
1092
  const eventSession = new EventSession(payload.prompt);
1007
1093
  const agent = await createAgentForJob(payload);
@@ -1037,7 +1123,7 @@ async function handleTheaterJob(api, ws, payload) {
1037
1123
  }
1038
1124
  }
1039
1125
  async function handleWorkflowJob(api, payload) {
1040
- const apmRoot = join6(payload.cwd, ".apm");
1126
+ const apmRoot = join7(payload.cwd, ".apm");
1041
1127
  console.log("[apm] ROOT:", apmRoot);
1042
1128
  console.log("[apm] workflow node:", payload.nodeId);
1043
1129
  try {
@@ -1163,17 +1249,17 @@ function runConnect(opts) {
1163
1249
  }
1164
1250
 
1165
1251
  // src/commands/init.ts
1166
- import { join as join7 } from "path";
1167
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
1252
+ import { join as join8 } from "path";
1253
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
1168
1254
  async function runInit(name) {
1169
1255
  await ensureWorkspaceApmDirForInit();
1170
1256
  await copyTemplateFiles(WORKSPACE_APM_DIR);
1171
1257
  if (name) {
1172
- const apmConfigPath = join7(WORKSPACE_APM_DIR, "apm.config.json");
1258
+ const apmConfigPath = join8(WORKSPACE_APM_DIR, "apm.config.json");
1173
1259
  const config = readFileSync4(apmConfigPath, "utf8");
1174
1260
  const configJson = JSON.parse(config);
1175
1261
  configJson.name = name;
1176
- writeFileSync4(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
1262
+ writeFileSync5(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
1177
1263
  }
1178
1264
  }
1179
1265
 
@@ -1368,10 +1454,10 @@ async function runBranch(requirementId, options = {}) {
1368
1454
 
1369
1455
  // src/commands/refine.ts
1370
1456
  import { readFileSync as readFileSync5 } from "fs";
1371
- import { join as join8 } from "path";
1457
+ import { join as join9 } from "path";
1372
1458
  async function runRefine(requirementId) {
1373
1459
  const cfg = await ensureLoggedConfig();
1374
- const filePath = join8(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
1460
+ const filePath = join9(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
1375
1461
  const content = readFileSync5(filePath, "utf8");
1376
1462
  const api = createApmApiClient(cfg);
1377
1463
  const data = await api.cliRequirements.refine({ requirementId, content });
@@ -1383,13 +1469,13 @@ import { spawnSync } from "child_process";
1383
1469
 
1384
1470
  // src/version.ts
1385
1471
  import { readFileSync as readFileSync6 } from "fs";
1386
- import { dirname as dirname2, join as join9 } from "path";
1472
+ import { dirname as dirname3, join as join10 } from "path";
1387
1473
  import { fileURLToPath as fileURLToPath2 } from "url";
1388
1474
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
1389
1475
  function readCliVersion() {
1390
1476
  try {
1391
- const dir = dirname2(fileURLToPath2(import.meta.url));
1392
- const pkgPath = join9(dir, "..", "package.json");
1477
+ const dir = dirname3(fileURLToPath2(import.meta.url));
1478
+ const pkgPath = join10(dir, "..", "package.json");
1393
1479
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
1394
1480
  return pkg.version ?? "0.0.0";
1395
1481
  } catch {
@@ -1458,8 +1544,8 @@ async function runUpdate() {
1458
1544
 
1459
1545
  // src/commands/update-skills.ts
1460
1546
  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");
1547
+ import { join as join11 } from "path";
1548
+ var TEMPLATE_SKILLS_DIR = join11(CLI_TEMPLATE_DIR, "skills");
1463
1549
  async function runUpdateSkills() {
1464
1550
  const apmDir = WORKSPACE_APM_DIR;
1465
1551
  if (!existsSync4(apmDir)) {
@@ -1479,7 +1565,7 @@ async function runUpdateSkills() {
1479
1565
  if (!templateStat.isDirectory()) {
1480
1566
  throw new Error(`[apm] \u5185\u7F6E\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_SKILLS_DIR}`);
1481
1567
  }
1482
- const skillsDir = join10(apmDir, "skills");
1568
+ const skillsDir = join11(apmDir, "skills");
1483
1569
  if (existsSync4(skillsDir)) {
1484
1570
  rmSync(skillsDir, { recursive: true, force: true });
1485
1571
  }
@@ -1488,6 +1574,77 @@ async function runUpdateSkills() {
1488
1574
  console.log("[apm] \u5DF2\u66F4\u65B0 .apm/skills");
1489
1575
  }
1490
1576
 
1577
+ // src/commands/update-theater-skills.ts
1578
+ import { cpSync as cpSync3, existsSync as existsSync5, rmSync as rmSync2, statSync as statSync4 } from "fs";
1579
+ import { join as join12 } from "path";
1580
+ var TEMPLATE_THEATER_SKILLS_DIR = join12(CLI_TEMPLATE_DIR, "theater-skills");
1581
+ async function runUpdateTheaterSkills() {
1582
+ const apmDir = WORKSPACE_APM_DIR;
1583
+ if (!existsSync5(apmDir)) {
1584
+ console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1585
+ process.exit(1);
1586
+ }
1587
+ const apmStat = statSync4(apmDir);
1588
+ if (!apmStat.isDirectory()) {
1589
+ throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1590
+ }
1591
+ let templateStat;
1592
+ try {
1593
+ templateStat = statSync4(TEMPLATE_THEATER_SKILLS_DIR);
1594
+ } catch {
1595
+ throw new Error(
1596
+ `[apm] \u5185\u7F6E\u5267\u573A\u6280\u80FD\u6A21\u677F\u4E0D\u5B58\u5728: ${TEMPLATE_THEATER_SKILLS_DIR}`
1597
+ );
1598
+ }
1599
+ if (!templateStat.isDirectory()) {
1600
+ throw new Error(
1601
+ `[apm] \u5185\u7F6E\u5267\u573A\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_THEATER_SKILLS_DIR}`
1602
+ );
1603
+ }
1604
+ const theaterSkillsDir = join12(apmDir, "theater-skills");
1605
+ if (existsSync5(theaterSkillsDir)) {
1606
+ rmSync2(theaterSkillsDir, { recursive: true, force: true });
1607
+ }
1608
+ await ensureDirExists(apmDir);
1609
+ cpSync3(TEMPLATE_THEATER_SKILLS_DIR, theaterSkillsDir, { recursive: true });
1610
+ console.log("[apm] \u5DF2\u66F4\u65B0 .apm/theater-skills");
1611
+ }
1612
+
1613
+ // src/commands/sync-session-document.ts
1614
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
1615
+ var DEFAULT_RELATIVE_PATH = "docs/prd.md";
1616
+ async function runSyncSessionDocument(sessionId, options) {
1617
+ const trimmedSessionId = sessionId.trim();
1618
+ if (!trimmedSessionId) {
1619
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1620
+ process.exit(1);
1621
+ }
1622
+ const relativePath = (options?.file?.trim() || DEFAULT_RELATIVE_PATH).replace(
1623
+ /\\/g,
1624
+ "/"
1625
+ );
1626
+ const apmRoot = options?.apmRoot ?? WORKSPACE_APM_DIR;
1627
+ const absPath = theaterSessionDocumentPath(
1628
+ trimmedSessionId,
1629
+ relativePath,
1630
+ apmRoot
1631
+ );
1632
+ if (!existsSync6(absPath)) {
1633
+ console.error(`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
1634
+ \u8BF7\u5148\u521B\u5EFA\u8BE5\u6587\u4EF6\u540E\u518D\u540C\u6B65`);
1635
+ process.exit(1);
1636
+ }
1637
+ const content = readFileSync7(absPath, "utf8");
1638
+ const cfg = await ensureLoggedConfig();
1639
+ const api = createApmApiClient(cfg);
1640
+ const data = await api.theaterSessionArtifact.upsert({
1641
+ sessionId: trimmedSessionId,
1642
+ fileName: relativePath,
1643
+ content
1644
+ });
1645
+ console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u6587\u6863: ${relativePath} (artifactId=${data.id})`);
1646
+ }
1647
+
1491
1648
  // src/commands/update-dev-status.ts
1492
1649
  async function runUpdateDevStatus(requirementId, status) {
1493
1650
  const cfg = await ensureLoggedConfig();
@@ -1514,19 +1671,19 @@ async function runUpdateStatus(requirementId, status) {
1514
1671
  import path5 from "node:path";
1515
1672
 
1516
1673
  // src/commands/deploy/internal/apm-config.ts
1517
- import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
1674
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
1518
1675
  import { resolve as resolve4 } from "node:path";
1519
1676
  function loadApmConfig(options) {
1520
1677
  const p = resolve4(
1521
1678
  process.cwd(),
1522
1679
  options?.configPath ?? resolve4(WORKSPACE_APM_DIR, "apm.config.json")
1523
1680
  );
1524
- if (!existsSync5(p)) {
1681
+ if (!existsSync7(p)) {
1525
1682
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
1526
1683
  process.exit(1);
1527
1684
  }
1528
1685
  try {
1529
- const raw = readFileSync7(p, "utf8");
1686
+ const raw = readFileSync8(p, "utf8");
1530
1687
  return JSON.parse(raw);
1531
1688
  } catch (e) {
1532
1689
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -1625,7 +1782,7 @@ import path4 from "node:path";
1625
1782
  import Docker from "dockerode";
1626
1783
 
1627
1784
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
1628
- import { existsSync as existsSync6, readFileSync as readFileSync8 } from "node:fs";
1785
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
1629
1786
  import path from "node:path";
1630
1787
  function asOptionalTlsBuffer(value) {
1631
1788
  if (typeof value !== "string") {
@@ -1637,8 +1794,8 @@ function asOptionalTlsBuffer(value) {
1637
1794
  if (normalized === "") {
1638
1795
  return void 0;
1639
1796
  }
1640
- if (existsSync6(normalized)) {
1641
- return readFileSync8(normalized);
1797
+ if (existsSync8(normalized)) {
1798
+ return readFileSync9(normalized);
1642
1799
  }
1643
1800
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
1644
1801
  if (looksLikePath) {
@@ -1848,7 +2005,7 @@ var DockerodeClient = class {
1848
2005
  var createDockerodeClient = (config) => new DockerodeClient(config);
1849
2006
 
1850
2007
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
1851
- import { existsSync as existsSync7, readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
2008
+ import { existsSync as existsSync9, readFileSync as readFileSync10, statSync as statSync5 } from "node:fs";
1852
2009
  import path2 from "node:path";
1853
2010
  function stripSurroundingQuotes(value) {
1854
2011
  const t = value.trim();
@@ -1865,10 +2022,10 @@ function loadEnvFromFile(envFilePath) {
1865
2022
  return {};
1866
2023
  }
1867
2024
  const targetPath = path2.resolve(envFilePath);
1868
- if (!existsSync7(targetPath) || !statSync4(targetPath).isFile()) {
2025
+ if (!existsSync9(targetPath) || !statSync5(targetPath).isFile()) {
1869
2026
  return {};
1870
2027
  }
1871
- const raw = readFileSync9(targetPath, "utf-8");
2028
+ const raw = readFileSync10(targetPath, "utf-8");
1872
2029
  const result = {};
1873
2030
  for (const line of raw.split(/\r?\n/)) {
1874
2031
  const normalized = line.trim();
@@ -2039,12 +2196,12 @@ function dockerPushImage(params, cwd) {
2039
2196
  }
2040
2197
 
2041
2198
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
2042
- import { existsSync as existsSync8 } from "node:fs";
2199
+ import { existsSync as existsSync10 } from "node:fs";
2043
2200
  import path3 from "node:path";
2044
2201
  function resolveDockerBuildPaths(cwd) {
2045
2202
  const dockerfilePath = path3.join(cwd, "Dockerfile");
2046
2203
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
2047
- if (!existsSync8(dockerfilePath)) {
2204
+ if (!existsSync10(dockerfilePath)) {
2048
2205
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
2049
2206
  }
2050
2207
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -2173,16 +2330,16 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
2173
2330
  import path7 from "node:path";
2174
2331
 
2175
2332
  // 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";
2333
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
2334
+ import { join as join13 } from "node:path";
2178
2335
  function loadApmDotEnvIfPresent() {
2179
- const p = join11(WORKSPACE_APM_DIR, ".env");
2180
- if (!existsSync9(p)) {
2336
+ const p = join13(WORKSPACE_APM_DIR, ".env");
2337
+ if (!existsSync11(p)) {
2181
2338
  return;
2182
2339
  }
2183
2340
  let text;
2184
2341
  try {
2185
- text = readFileSync10(p, "utf8");
2342
+ text = readFileSync11(p, "utf8");
2186
2343
  } catch {
2187
2344
  return;
2188
2345
  }
@@ -2207,14 +2364,14 @@ function loadApmDotEnvIfPresent() {
2207
2364
  }
2208
2365
 
2209
2366
  // src/commands/deploy/internal/minio.ts
2210
- import { statSync as statSync5 } from "node:fs";
2367
+ import { statSync as statSync6 } from "node:fs";
2211
2368
  import { readdir, readFile } from "node:fs/promises";
2212
2369
  import path6 from "node:path";
2213
2370
  import * as Minio from "minio";
2214
2371
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
2215
2372
  async function isDirectoryPath(dir) {
2216
2373
  try {
2217
- const st = statSync5(dir);
2374
+ const st = statSync6(dir);
2218
2375
  return st.isDirectory();
2219
2376
  } catch {
2220
2377
  return false;
@@ -2244,7 +2401,7 @@ async function collectFiles(root) {
2244
2401
  if (e.isDirectory()) {
2245
2402
  await walk(abs, rel);
2246
2403
  } else if (e.isFile()) {
2247
- const st = statSync5(abs);
2404
+ const st = statSync6(abs);
2248
2405
  out.push({
2249
2406
  absPath: abs,
2250
2407
  relativePath: rel.replace(/\\/g, "/"),
@@ -2566,6 +2723,25 @@ function buildProgram() {
2566
2723
  ).action(async () => {
2567
2724
  await runUpdateSkills();
2568
2725
  });
2726
+ program.command("update-theater-skills").description(
2727
+ "\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"
2728
+ ).action(async () => {
2729
+ await runUpdateTheaterSkills();
2730
+ });
2731
+ program.command("pull-session").description(
2732
+ "\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"
2733
+ ).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").action(async (sessionId) => {
2734
+ const dir = await runPullTheaterSession(sessionId);
2735
+ console.log(`[apm] \u4F1A\u8BDD\u6587\u6863\u76EE\u5F55: ${dir}`);
2736
+ });
2737
+ program.command("sync-session-document").description(
2738
+ "\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
+ ).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").option(
2740
+ "--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"
2742
+ ).action(async (sessionId, opts) => {
2743
+ await runSyncSessionDocument(sessionId, { file: opts.file });
2744
+ });
2569
2745
  program.command("pull").description(
2570
2746
  "GET /api/cli/requirements/pull\uFF0C\u540C\u6B65\u6570\u636E\u4E0E\u9644\u4EF6\u5230 .apm/workitems/<\u9700\u6C42ID>"
2571
2747
  ).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "4.0.21",
3
+ "version": "4.0.22",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,13 @@
1
+ ## 剧场技能
2
+
3
+ 供剧场(Theater)会话中各角色 Agent 使用的技能,由 `apm init` 复制到 `.apm/theater-skills/`,可用 `apm update-theater-skills` 从 CLI 内置模板重置。
4
+
5
+ `THEATER_JOB` 开始时会自动 `pull-session`,将平台会话文档下载到 `.apm/theater-sessions/<sessionId>/docs/`。`docs/` 下文档变更后通过 `apm sync-session-document` 同步到平台;**Agent 对话日志不上传**。
6
+
7
+ ## 目录
8
+
9
+ | 技能 | 说明 |
10
+ | ---------------------- | -------------------------------------------------------------------------------------------------------- |
11
+ | `apm-theater-prd/` | 产品经理:维护会话 PRD(`.apm/theater-sessions/<sessionId>/docs/prd.md`)并同步到平台 |
12
+ | `apm-theater-backend/` | 后端:`docs/backend.md`(技术评审实现思路)、`docs/api.md`(前端联调接口说明);增删改查与同步规则同 PRD |
13
+ | `apm-theater-dev/` | 开发:按 `sessionId` 全自动编码(切分支、Quick/Spec、提交推送、同步 `docs/` 文档) |
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: apm-theater-backend
3
+ description: 剧场会话后端文档:在 .apm/theater-sessions/<sessionId>/docs/ 维护 backend.md(技术评审用实现思路)与 api.md(前端联调用接口说明),落盘后同步到平台会话文档。仅当用户 @ 本技能时使用。
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # 剧场会话后端文档
8
+
9
+ 在剧场会话上下文中维护后端交付文档:
10
+
11
+ | 文件 | 用途 |
12
+ | --------------------- | ------------------------------------------------------------ |
13
+ | **`docs/backend.md`** | **后端技术文档**:如何实现该需求,实现思路写清,供技术评审 |
14
+ | **`docs/api.md`** | **接口联调文档**:涉及接口与参数,给前端;多接口协作须写清楚 |
15
+
16
+ 本地路径均在 **`.apm/theater-sessions/<sessionId>/docs/`**;落盘后须通过 CLI 同步到平台「会话文档」。
17
+
18
+ `THEATER_JOB` 开始时会自动 pull 平台文档到本地。Agent **对话日志不上传**;仅 `docs/` 下业务文档在变更后同步。
19
+
20
+ ---
21
+
22
+ ## 输入
23
+
24
+ | 字段 | 规则 |
25
+ | --------------- | -------------------------------------------------------------------------------------- |
26
+ | **`sessionId`** | **必填**。剧场会话 ID,与 `.apm/theater-sessions/<sessionId>/` 目录名一致。 |
27
+ | **目标文档** | 按用户意图选择 `backend.md` 或 `api.md`;未指明时结合上下文判断,仍不明则询问。 |
28
+ | **会话上下文** | 若由 `THEATER_JOB` 触发,`theaterSessionId` 即 `sessionId`;缺省时向用户索要,不猜测。 |
29
+
30
+ ---
31
+
32
+ ## 文件路径
33
+
34
+ 以下规则对 **`backend.md`**、**`api.md`** 均适用(将 `<文件名>` 替换为对应文件):
35
+
36
+ | 操作 | 路径 / 方式 |
37
+ | -------- | ------------------------------------------------------------------------------------- |
38
+ | **新增** | 创建 `.apm/theater-sessions/<sessionId>/docs/<文件名>`(`docs` 目录不存在时一并创建) |
39
+ | **编辑** | 更新上述文件(Write / StrReplace) |
40
+ | **查阅** | 使用 **Read** 工具阅读上述文件 |
41
+ | **删除** | **不支持**。不要删除该文件,也不要尝试从平台删除对应文档。 |
42
+
43
+ ---
44
+
45
+ ## 同步到平台(新增 / 编辑后必做)
46
+
47
+ 本地文档 **新增或内容变更后**执行:
48
+
49
+ ```bash
50
+ apm sync-session-document <sessionId> --file docs/backend.md
51
+ apm sync-session-document <sessionId> --file docs/api.md
52
+ ```
53
+
54
+ - 命令会调用平台接口 **新增或更新** 该会话的产物文档(按 `fileName` 幂等)。
55
+ - **查阅**(Read)**不需要**执行同步。
56
+ - **不要**同步 Agent 对话日志(`sessions/` 等);仅同步 `docs/` 业务文档。
57
+ - 同步失败时:在回复中说明错误,**不要**声称已写入平台。
58
+
59
+ ---
60
+
61
+ ## 撰写要求
62
+
63
+ 撰写前建议 **Read** `.apm/theater-sessions/<sessionId>/docs/prd.md`(若存在),与需求范围对齐。
64
+
65
+ ### `backend.md`(技术评审)
66
+
67
+ 须 **Read** [apm-theater-backend-template.md](./apm-theater-backend-template.md),按模板**章节粒度**组织正文(需求摘要 → 方案总览 → 库表 → 分层 → 核心流程 → 实现状态 → 待办 → 风险)。核心是 **实现思路写清楚**;接口参数明细放 `api.md`。
68
+
69
+ ### `api.md`(前端联调)
70
+
71
+ 须 **Read** [apm-theater-api-template.md](./apm-theater-api-template.md),按模板**章节粒度**组织正文(基础约定 → 数据模型 → 接口清单 → 接口明细 → 页面配合 → 错误场景 → 联调清单)。写清每个功能的**方法、路径、参数、响应**;多接口协作须写**步骤、顺序、副作用与前端展示规则**。
72
+
73
+ ---
74
+
75
+ ## 流程
76
+
77
+ ### 1. 锚定会话与文档
78
+
79
+ 确认 **`sessionId`** 与目标文件(`backend.md` / `api.md`)。
80
+
81
+ ### 2. 查阅(只读)
82
+
83
+ **Read** `.apm/theater-sessions/<sessionId>/docs/<文件名>`。
84
+
85
+ - 文件不存在:说明尚未创建,询问是否新建;**不要**编造正文。
86
+
87
+ ### 3. 新增 `backend.md`
88
+
89
+ 1. **Read** `prd.md`(若存在)与 [apm-theater-backend-template.md](./apm-theater-backend-template.md)。
90
+ 2. **Write** 落盘至 `.apm/theater-sessions/<sessionId>/docs/backend.md`。
91
+ 3. **`apm sync-session-document <sessionId> --file docs/backend.md`**。
92
+
93
+ ### 4. 新增 `api.md`
94
+
95
+ 1. **Read** `prd.md`、必要时 `backend.md`,以及 [apm-theater-api-template.md](./apm-theater-api-template.md)。
96
+ 2. **Write** 落盘至 `.apm/theater-sessions/<sessionId>/docs/api.md`。
97
+ 3. **`apm sync-session-document <sessionId> --file docs/api.md`**。
98
+
99
+ ### 5. 编辑
100
+
101
+ 1. **Read** 现有文件与对应模板(若本会话尚未读过)。
102
+ 2. **Write** 或 **StrReplace** 落盘。
103
+ 3. **`apm sync-session-document <sessionId> --file docs/<文件名>`**。
104
+ 4. 简要说明变更要点。
105
+
106
+ ---
107
+
108
+ ## Guardrails
109
+
110
+ - **不要删除** `backend.md`、`api.md`,也不要删除平台会话文档。
111
+ - **不要**在无 `sessionId` 时写入任意目录。
112
+ - **新增或编辑后必须同步** `docs/` 文档;仅 Read 时不同步。
113
+ - **不要**上传或同步 Agent 对话日志。
114
+ - 同步失败时如实报告,不假装成功。
115
+ - `backend.md` 与 `api.md` 分工明确:方案在 backend,接口契约在 api;待确认项写入文档,避免阻塞主文。
@@ -0,0 +1,146 @@
1
+ # 接口联调文档模板
2
+
3
+ **定位**:给**前端**联调用的接口说明——写清涉及到的接口、参数、响应与页面配合方式;若某功能需**多个接口配合**(如先上传再绑定),须写清**步骤、顺序与副作用**。
4
+
5
+ 撰写前建议 **Read** `prd.md`、必要时 **Read** `backend.md`,与需求及后端方案对齐。
6
+
7
+ ---
8
+
9
+ ## 正文骨架
10
+
11
+ 按下列章节组织(编号可依项目调整,但**顺序与粒度**保持一致):
12
+
13
+ ```markdown
14
+ # [需求标题] — 接口联调文档
15
+
16
+ > 后端模块:`XxxController`(`/模块前缀`)
17
+
18
+ ## 1. 基础约定
19
+
20
+ ### 1.1 Base URL
21
+
22
+ (开发/测试示例地址;是否带 context-path)
23
+
24
+ ### 1.2 鉴权
25
+
26
+ (Header / Token 名称;本期是否有额外权限参数)
27
+
28
+ ### 1.3 统一响应体
29
+
30
+ (字段表 + 成功/失败 JSON 示例;写明业务数据在哪个字段,如 `result` / `data`)
31
+
32
+ ### 1.4 特殊接口说明(若有)
33
+
34
+ (如:文件流下载无 JSON 包装、预览返回路径给现网组件等)
35
+
36
+ ---
37
+
38
+ ## 2. 数据模型 `[实体名]`
39
+
40
+ 列表、详情、编辑共用的核心字段表(字段 | 类型 | 说明);**前端展示规则**写进说明列(如某字段为 `null` 时列表留空)。
41
+
42
+ ---
43
+
44
+ ## 3. 接口清单
45
+
46
+ | 方法 | 路径 | 说明 |
47
+ | ---- | ---- | ---- |
48
+ | … | … | … |
49
+
50
+ 未落地接口在说明列标 **待后端实现/提供**。
51
+
52
+ ---
53
+
54
+ ## 4. 接口明细
55
+
56
+ ### 4.1 [接口名称]
57
+
58
+ **[METHOD]** `完整路径`
59
+
60
+ **Query / Body 参数表**(参数 | 必填 | 默认 | 说明)
61
+
62
+ **响应**(`result` / `data` 结构;复杂时给 JSON 示例)
63
+
64
+ **前端展示/交互要点**(与本接口直接相关的 UI 规则)
65
+
66
+ **说明**(业务副作用、与 PRD 的对应关系)
67
+
68
+ ### 4.2 …
69
+
70
+ #### 多步骤协作(如「行内上传 + 绑定」)
71
+
72
+ 分步骤写清,每步单独小节:
73
+
74
+ 1. **步骤 1**:上传接口 — 入参、成功时哪个字段是后续要用的路径
75
+ 2. **步骤 2**:编辑/保存接口 — Body 示例(首次 / 更换两种)、副作用(如是否更新 `submitDate`)、成功后是否刷新列表
76
+
77
+ **注意**:只读页面不调写接口等 PRD 约束写在本节。
78
+
79
+ ### 4.x 易混淆接口对比(若存在)
80
+
81
+ 用表格对比「入口 | 接口 | 内容」,避免前端联调混用。
82
+
83
+ ---
84
+
85
+ ## 5. 前端页面与接口配合
86
+
87
+ 按页面用缩进树或列表写调用链,例如:
88
+
89
+ ### 5.1 列表页
90
+
91
+ 加载列表 → GET /list
92
+ ├─ 新增 → …
93
+ ├─ 删除 → …
94
+ └─ 某列操作 → 多步 upload → edit
95
+
96
+ ### 5.2 新增页 / 详情页
97
+
98
+
99
+
100
+ ---
101
+
102
+ ## 6. 错误场景
103
+
104
+ | 场景 | 表现 |
105
+ | ---- | --------------------------------------- |
106
+ | … | `success=false` 的 message 或 HTTP 状态 |
107
+
108
+ ---
109
+
110
+ ## 7. 联调检查清单
111
+
112
+ - [ ] …
113
+ - [ ] …
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 写法要点
119
+
120
+ | 类型 | 写法要点 |
121
+ | ---------- | --------------------------------------------------------------------------- |
122
+ | 接口清单 | 先总表后明细;路径、方法、一句话说明齐全 |
123
+ | 单接口 | 参数表 + 响应结构 + **前端展示要点**;复杂 Body 给完整 JSON 示例 |
124
+ | 多接口协作 | 分步骤编号;每步写清入参、成功判据、传给下一步的字段;写副作用与是否刷新 |
125
+ | 数据模型 | 共用实体一次定义;`null` / 空值的 UI 处理写在字段说明或列表要点里 |
126
+ | 页面配合 | 按列表/新增/详情分节;用树状流程串起接口,不只堆接口表 |
127
+ | 易混淆能力 | 同类下载/预览等多入口时,用对比表说明差异 |
128
+ | 待实现 | 明确标 **待后端实现**;错误场景与检查清单可预留项 |
129
+ | 项目约定 | 遵循目标项目既有风格(如 Jeecg `Result`、APM `{ data }`);不擅自改路径规范 |
130
+
131
+ ---
132
+
133
+ ## 落盘前自检
134
+
135
+ - [ ] 含 **基础约定、数据模型、接口清单、接口明细、页面配合、错误场景、联调清单**
136
+ - [ ] 每个 PRD 功能点在「页面配合」或明细中能找到对应接口
137
+ - [ ] 多步骤流程(上传+保存等)顺序与字段传递写清
138
+ - [ ] 列表/详情/空值展示规则前端可直接照做
139
+ - [ ] 与 `backend.md` 不矛盾;未实现接口已标注
140
+ - [ ] 检查清单覆盖 PRD 关键验收点
141
+
142
+ ---
143
+
144
+ ## 局部替换
145
+
146
+ 只改部分接口或页面时:同步更新 **§3 清单**、**§4 明细**、**§5 页面配合** 与 **§7 检查清单** 中相关条目。
@@ -0,0 +1,132 @@
1
+ # 后端技术文档模板
2
+
3
+ **定位**:说明后端**准备如何实现该需求**,实现思路写清楚,供**技术评审**。接口参数与前端联调细节放在 `api.md`,本文聚焦方案、数据、流程与实现状态。
4
+
5
+ 撰写前须 **Read** `.apm/theater-sessions/<sessionId>/docs/prd.md`(若存在)。
6
+
7
+ ---
8
+
9
+ ## 正文骨架
10
+
11
+ ```markdown
12
+ # [需求标题] — 后端技术方案
13
+
14
+ > 关联 PRD:[prd.md](./prd.md)
15
+
16
+ ## 1. 需求摘要
17
+
18
+ (一段话概括本期做什么;**本期不做**单独列出)
19
+
20
+ ---
21
+
22
+ ## 2. 方案总览
23
+
24
+ | 项 | 说明 |
25
+ | -------- | ----------------- |
26
+ | 模块 | … |
27
+ | 数据表 | … |
28
+ | 接口前缀 | … |
29
+ | 实现风格 | … |
30
+ | 代码现状 | 已落地 / 待完善项 |
31
+
32
+ (与同类既有模块的差异、核心设计决策 1 ~ 3 句)
33
+
34
+ ---
35
+
36
+ ## 3. 数据库设计
37
+
38
+ (迁移脚本路径若有则写)
39
+
40
+ | 字段 | 类型 | 说明 |
41
+ | ---- | ---- | ---- |
42
+ | … | … | … |
43
+
44
+ **索引**、**约束**、注释与命名注意点。
45
+
46
+ ---
47
+
48
+ ## 4. 后端分层结构
49
+
50
+ (目录树或包结构:entity / mapper / service / controller)
51
+
52
+ ---
53
+
54
+ ## 5. 核心业务流程
55
+
56
+ 按 PRD 场景分小节(如 5.1 新增、5.2 行内上传…),每节写清:
57
+
58
+ 1. 步骤顺序
59
+ 2. 校验规则与 **不做什么**
60
+ 3. 副作用(写库、写文件、更新时间等)
61
+ 4. **待实现** 项单独标出
62
+
63
+ 多接口协作写清与通用能力(如上传)的组合方式;易混淆能力用表格对比。
64
+
65
+ ---
66
+
67
+ ## 6. 接口能力对照(实现状态)
68
+
69
+ | 能力 | 状态 | 备注 |
70
+ | ---- | ------------ | ---- |
71
+ | … | ✅ / ⚠️ / ❌ | … |
72
+
73
+ (评审用速览表;细节不在此重复,见 `api.md`)
74
+
75
+ ---
76
+
77
+ ## 7. 菜单与权限(若涉及)
78
+
79
+ ---
80
+
81
+ ## 8. 配置与部署
82
+
83
+ | 配置 | 说明 |
84
+ | ---- | ---- |
85
+ | … | … |
86
+
87
+ ---
88
+
89
+ ## 9. 待办清单(开发任务)
90
+
91
+ 1. …
92
+ 2. …
93
+
94
+ ---
95
+
96
+ ## 10. 风险与注意点
97
+
98
+ - …
99
+ ```
100
+
101
+ 按需删减章节(如无独立菜单配置可省略 §7);**核心业务流程**与 **待办清单** 不可省。
102
+
103
+ ---
104
+
105
+ ## 写法要点
106
+
107
+ | 类型 | 写法要点 |
108
+ | ---------- | ----------------------------------------------------------------------- |
109
+ | 需求摘要 | 与 PRD 一一对应;**本期不做** 写清,避免评审范围漂移 |
110
+ | 方案总览 | 模块、表、接口前缀、与存量模块关系;**代码现状** 如实(已做/待补) |
111
+ | 数据库 | 字段业务含义、NULL 语义、何时写入;索引与 PRD 约束(如不做唯一性)对齐 |
112
+ | 核心流程 | 分场景编号;写清两步/多步协作、副作用、与 PRD 差异;**待实现** 醒目标注 |
113
+ | 实现状态表 | 能力粒度与 PRD 验收点对齐;用 ✅ ⚠️ ❌ 或文字状态 |
114
+ | 待办清单 | 可执行任务,与 §6 中未完成项对应 |
115
+ | 风险 | 安全、磁盘、与相似模块混淆等;不写接口 JSON |
116
+
117
+ ---
118
+
119
+ ## 落盘前自检
120
+
121
+ - [ ] 评审人不读代码也能理解**怎么做、差什么**
122
+ - [ ] 与 `prd.md` 范围一致,「本期不做」无遗漏
123
+ - [ ] 核心流程覆盖 PRD 各场景,含多步协作与副作用
124
+ - [ ] §6 实现状态与 §9 待办一致
125
+ - [ ] 接口契约细节在 `api.md`,本文不重复参数表
126
+ - [ ] 未臆造已完成功能;待做项已列出
127
+
128
+ ---
129
+
130
+ ## 局部替换
131
+
132
+ 只改部分能力时:同步更新 **§5 流程**、**§6 状态表**、**§9 待办** 与 **§10 风险** 中相关条目。
@@ -0,0 +1,174 @@
1
+ ---
2
+ name: apm-theater-dev
3
+ description: 按剧场会话 ID 全自动编码:切分支、读会话 PRD(及 backend/api 文档)、按改动规模选择 Quick 或 Spec、提交并推送、同步 docs 产物;子 Agent 承担编码与规划落地;当用户 @ 本技能、提及剧场全自动开发或「apm-theater-dev」时使用。
4
+ ---
5
+
6
+ # 剧场全自动开发(按会话 ID)
7
+
8
+ 用户给出 **剧场会话 ID**(`sessionId`,与 `.apm/theater-sessions/<sessionId>/` 目录名一致)。**缺 ID 时索要,不猜测。**
9
+
10
+ `THEATER_JOB` 开始时会自动 pull 平台文档到本地。Agent **对话日志不上传**;`docs/` 等业务文档在变更后须 `sync-session-document`。
11
+
12
+ ---
13
+
14
+ ## 输入
15
+
16
+ | 字段 | 规则 |
17
+ | --------------- | -------------------------------------------------------------------------------------- |
18
+ | **`sessionId`** | **必填**。与 `.apm/theater-sessions/<sessionId>/` 目录名一致。 |
19
+ | **会话上下文** | 若由 `THEATER_JOB` 触发,`theaterSessionId` 即 `sessionId`;缺省时向用户索要,不猜测。 |
20
+
21
+ **会话根目录**:`.apm/theater-sessions/<sessionId>/`
22
+ **PRD**:`.apm/theater-sessions/<sessionId>/docs/prd.md`
23
+ **可选参考**:`docs/backend.md`、`docs/api.md`
24
+
25
+ ---
26
+
27
+ ## 技能文件定位(apm-propose / apm-apply-change)
28
+
29
+ Spec 路径依赖的技能**仅**来自 **`.apm/skills/`**。执行前父 Agent **须 Read**:
30
+
31
+ | 技能 | 路径 |
32
+ | -------------------- | --------------------------------------- |
33
+ | **apm-propose** | `.apm/skills/apm-propose/SKILL.md` |
34
+ | **apm-apply-change** | `.apm/skills/apm-apply-change/SKILL.md` |
35
+
36
+ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-instruction.md`)。若上述 `SKILL.md` **不存在或不可读**:**不得**进入 Spec 子流程;在表格中标记失败原因(例如需先 `apm init` 或 `apm update-skills`),并停止步骤 4。
37
+
38
+ 委派子 Agent 时须说明:本轮为**剧场会话**,**`sessionId`** 替代需求 ID;PRD 在 **`docs/prd.md`**;Spec 工件落在 **`.apm/theater-sessions/<sessionId>/`**(非 `workitems`)。
39
+
40
+ ---
41
+
42
+ ## 流程总览
43
+
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
+ ```
64
+
65
+ 若远端已有该分支:`git switch feat/theater-<sessionId>` 后 `git pull --no-edit`(按需)。
66
+
67
+ 2. 记录:是否成功、当前分支名、简要输出或错误(写入最终表格)。**失败则按「Guardrails → 失败即终止」处理**。
68
+
69
+ ---
70
+
71
+ ## 步骤 2:读取 PRD 并评估改动成本
72
+
73
+ 1. **Read** 全文:`.apm/theater-sessions/<sessionId>/docs/prd.md`。
74
+ 2. 若实现涉及后端/接口,按需 **Read** `docs/backend.md`、`docs/api.md`。
75
+ 3. 若 `prd.md` 读失败则**停止后续实现**,仅在表格中标记失败原因。
76
+ 4. **不**为评估向用户发起追问;信息不足时倾向 **Spec**(保守)。
77
+
78
+ ### Quick(较小)与 Spec(较大)判定参考
79
+
80
+ **倾向 Quick**(满足越多越适用):
81
+
82
+ - 影响范围局部:少量文件或单一层次(例如仅前端组件、或仅一个后端模块小改)。
83
+ - 无新表结构/大规模迁移/权限模型变更。
84
+ - PRD 验收点清晰且数量少(经验上 **≤3** 条独立验收维度)。
85
+ - 不需要跨多服务的架构裁定即可开工。
86
+
87
+ **倾向 Spec**(命中任一条即可):
88
+
89
+ - 前后端联动、多包改造或新公共抽象。
90
+ - 新数据模型、迁移、或安全/审计/权限相关。
91
+ - PRD 范围大、条款多,或存在明显「待确认/多方案」需先规划。
92
+ - 评估认为不先产出 **proposal / design / specs / tasks** 则难以保证实现与验收对齐。
93
+
94
+ 在表格「步骤 2」中写明结论:**Quick** 或 **Spec**,以及**一行内**理由(关键词即可)。
95
+
96
+ ---
97
+
98
+ ## 步骤 3:Quick 开发模式(子 Agent)
99
+
100
+ **条件**:步骤 2 判定为 **Quick**。
101
+
102
+ 1. 父 Agent 已通过 **Read** 掌握 `prd.md`(及需要的 backend/api);委派时在提示中写明 **`sessionId`**、会话根路径、以及「实现须严格对照 PRD,改动范围最小化」。
103
+ 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)。
106
+ - 完成后在返回中说明:改了哪些路径、如何对照验收、是否通过本地可执行的检查(若跑了 `rushx build` / 测试等则写明结果)。
107
+ 3. 父 Agent 根据子 Agent 返回在表格中填写步骤 3 **状态**;本模式下步骤 4 填 **跳过**。
108
+
109
+ ---
110
+
111
+ ## 步骤 4:Spec 开发模式(子 Agent)
112
+
113
+ **条件**:步骤 2 判定为 **Spec**。
114
+
115
+ 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 为准)。
117
+ 3. **子 Agent B(实现)**:待 A 成功落盘后,再 Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-apply-change/SKILL.md` 并遵循:按 **`.apm/theater-sessions/<sessionId>/tasks.md`** 驱动实现与勾选。
118
+ 4. 若未产出可用 **`tasks.md`**,不得强行进入 **apm-apply-change**;表格中标记阻塞原因。
119
+ 5. 表格中步骤 3 填 **跳过**;步骤 4 分两行或合并一行写清 propose / apply 状态。
120
+
121
+ ---
122
+
123
+ ## 步骤 5:提交并推送
124
+
125
+ 在仓库根目录执行:
126
+
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
+ ```
144
+
145
+ - **仅同步业务文档**;**不要**同步 Agent 对话日志(`sessions/` 等)。
146
+ - 若本轮未改动任何需上平台的文档,表格步骤 6 填 **跳过**。
147
+ - 同步失败时如实记录,不假装成功。
148
+
149
+ ---
150
+
151
+ ## 步骤 7:对用户回复(表格)
152
+
153
+ 对用户回复 **必须包含一张 Markdown 表格**,汇总 **步骤 1 ~ 6**。表头建议:
154
+
155
+ | 步骤 | 内容 | 结果 |
156
+ | ---- | ------------------------------------------------ | ------------------------------ |
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`(有改动的文档) | 成功 / 失败 / **跳过** |
163
+
164
+ **可选**:表格外增加**简短**摘要(当前分支、阻塞点)。
165
+
166
+ ---
167
+
168
+ ## Guardrails
169
+
170
+ - **失败即终止**:在用户提供的 **`sessionId`** 等参数合法、命令与路径按本技能书写的前提下,任一步骤(含切分支、读文件、子 Agent、`git`、同步文档)**一旦失败**:**停止后续所有步骤**,仅在表格中记录失败步骤与原因;**不要**为登录、依赖、网络等做**多次**重试。
171
+ - **例外(Agent 自身失误)**:若失败明显由执行 Agent **用错工作目录、读错/漏写路径** 等导致,**允许**纠正后**仅对该失败步骤再执行一次**;纠正后仍失败则**立即终止**。
172
+ - **不要**在无 `prd.md` 的情况下编造需求实现。
173
+ - **不要**上传或同步 Agent 对话日志。
174
+ - **子 Agent** 提示中须带 **`sessionId`** 与会话根路径,避免改错工作树。
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: apm-theater-prd
3
+ description: 剧场会话 PRD 文档:在 .apm/theater-sessions/<sessionId>/docs/prd.md 新增、编辑、查阅需求正文,落盘后同步到平台会话文档。仅当用户 @ 本技能时使用。
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # 剧场会话 PRD
8
+
9
+ 在剧场会话上下文中维护 **产品需求文档(PRD)**。本地权威路径为 **`.apm/theater-sessions/<sessionId>/docs/prd.md`**;落盘后须通过 CLI 同步到平台「会话文档」。
10
+
11
+ `THEATER_JOB` 开始时会自动 pull 平台文档到本地。Agent **对话日志不上传**;仅 `docs/` 下业务文档在变更后同步。
12
+
13
+ ---
14
+
15
+ ## 输入
16
+
17
+ | 字段 | 规则 |
18
+ | --------------- | -------------------------------------------------------------------------------------- |
19
+ | **`sessionId`** | **必填**。剧场会话 ID,与 `.apm/theater-sessions/<sessionId>/` 目录名一致。 |
20
+ | **会话上下文** | 若由 `THEATER_JOB` 触发,`theaterSessionId` 即 `sessionId`;缺省时向用户索要,不猜测。 |
21
+
22
+ ---
23
+
24
+ ## 文件路径
25
+
26
+ | 操作 | 路径 / 方式 |
27
+ | -------- | ----------------------------------------------------------------------------------- |
28
+ | **新增** | 创建 `.apm/theater-sessions/<sessionId>/docs/prd.md`(`docs` 目录不存在时一并创建) |
29
+ | **编辑** | 更新上述文件(Write / StrReplace) |
30
+ | **查阅** | 使用 **Read** 工具阅读上述文件 |
31
+ | **删除** | **不支持**。不要删除该文件,也不要尝试从平台删除对应文档。 |
32
+
33
+ ---
34
+
35
+ ## 同步到平台(新增 / 编辑后必做)
36
+
37
+ 本地 `prd.md` **新增或内容变更后**执行:
38
+
39
+ ```bash
40
+ apm sync-session-document <sessionId>
41
+ ```
42
+
43
+ - 默认同步 `docs/prd.md`;若同步其他相对路径,加 `--file <相对路径>`(相对于 `.apm/theater-sessions/<sessionId>/`)。
44
+ - 命令会调用平台接口 **新增或更新** 该会话的产物文档(按 `fileName` 幂等)。
45
+ - **查阅**(Read)**不需要**执行同步。
46
+ - **不要**同步 Agent 对话日志(`sessions/` 等);仅同步 `docs/` 业务文档。
47
+ - 同步失败时:在回复中说明错误,**不要**声称已写入平台。
48
+
49
+ ---
50
+
51
+ ## 撰写要求(PRD 正文)
52
+
53
+ 撰写或修订 `prd.md` 前,须 **Read** [apm-refine-template.md](../../skills/apm-refine/apm-refine-template.md),按其中正文骨架、需求点写法、示例与落盘自检组织正文。
54
+
55
+ 在此基础上补充:
56
+
57
+ - **可验收**:产品、测试、开发不读代码也能写用例。
58
+ - **业务化**:写界面、流程、规则与系统表现;避免堆砌实现细节。
59
+ - **术语统一**:同一概念全文同一叫法。
60
+
61
+ ---
62
+
63
+ ## 流程
64
+
65
+ ### 1. 锚定会话
66
+
67
+ 确认 **`sessionId`**。根目录:`.apm/theater-sessions/<sessionId>/docs/`。
68
+
69
+ ### 2. 查阅(只读)
70
+
71
+ **Read** `.apm/theater-sessions/<sessionId>/docs/prd.md`。
72
+
73
+ - 文件不存在:说明尚未创建,询问是否新建;**不要**编造正文。
74
+
75
+ ### 3. 新增
76
+
77
+ 1. **Read** [apm-refine-template.md](../../skills/apm-refine/apm-refine-template.md),按模板撰写完整 PRD 正文。
78
+ 2. **Write** 落盘至 `.apm/theater-sessions/<sessionId>/docs/prd.md`。
79
+ 3. 执行 **`apm sync-session-document <sessionId>`**。
80
+ 4. 简要确认:本地路径、已同步到会话文档。
81
+
82
+ ### 4. 编辑
83
+
84
+ 1. **Read** 现有 `prd.md`(若本会话尚未读过)。
85
+ 2. 按需 **Read** [apm-refine-template.md](../../skills/apm-refine/apm-refine-template.md);按用户或场景要求修订;**Write** 或 **StrReplace** 落盘。
86
+ 3. 执行 **`apm sync-session-document <sessionId>`**。
87
+ 4. 简要说明变更要点。
88
+
89
+ ---
90
+
91
+ ## Guardrails
92
+
93
+ - **不要删除** `prd.md`,也不要删除平台会话文档。
94
+ - **不要**在无 `sessionId` 时写入任意目录。
95
+ - **新增或编辑后必须同步** `docs/` 文档;仅 Read 时不同步。
96
+ - **不要**上传或同步 Agent 对话日志。
97
+ - 同步失败时如实报告,不假装成功。
98
+ - 向用户澄清范围时,歧义写入 PRD 的「待确认」或「假设」,避免阻塞整篇文档。