ai-project-manage-cli 4.0.20 → 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 +209 -33
- package/package.json +1 -1
- package/template/skills/apm-deploy/SKILL.md +37 -34
- package/template/theater-skills/README.md +13 -0
- package/template/theater-skills/apm-theater-backend/SKILL.md +115 -0
- package/template/theater-skills/apm-theater-backend/apm-theater-api-template.md +146 -0
- package/template/theater-skills/apm-theater-backend/apm-theater-backend-template.md +132 -0
- package/template/theater-skills/apm-theater-dev/SKILL.md +174 -0
- package/template/theater-skills/apm-theater-prd/SKILL.md +98 -0
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
|
|
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 =
|
|
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
|
|
1167
|
-
import { readFileSync as readFileSync4, writeFileSync as
|
|
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 =
|
|
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
|
-
|
|
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
|
|
1457
|
+
import { join as join9 } from "path";
|
|
1372
1458
|
async function runRefine(requirementId) {
|
|
1373
1459
|
const cfg = await ensureLoggedConfig();
|
|
1374
|
-
const filePath =
|
|
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
|
|
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 =
|
|
1392
|
-
const pkgPath =
|
|
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
|
|
1462
|
-
var TEMPLATE_SKILLS_DIR =
|
|
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 =
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
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 (
|
|
1641
|
-
return
|
|
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
|
|
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 (!
|
|
2025
|
+
if (!existsSync9(targetPath) || !statSync5(targetPath).isFile()) {
|
|
1869
2026
|
return {};
|
|
1870
2027
|
}
|
|
1871
|
-
const raw =
|
|
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
|
|
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 (!
|
|
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
|
|
2177
|
-
import { join as
|
|
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 =
|
|
2180
|
-
if (!
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: apm-deploy
|
|
3
|
-
description: 路径相对仓库根 `<repoRoot>`;`.apm` 常被 gitignore,须直接 Read 或用 Shell 验磁盘,勿仅靠索引/搜索。用户仅提供需求 ID
|
|
3
|
+
description: 路径相对仓库根 `<repoRoot>`;`.apm` 常被 gitignore,须直接 Read 或用 Shell 验磁盘,勿仅靠索引/搜索。用户仅提供需求 ID;目标部署环境从当前任务说明读取(勿读 requirement-status.yaml 的 env);读 deploy README 与 workitems 后按 README 执行并汇总;不足则终止;@ 本技能、自动部署或「apm-deploy」时使用。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# APM 自动部署(按 `.apm/deploy/README.md`)
|
|
7
7
|
|
|
8
8
|
- **部署怎么做**:**唯一**以仓库 **`.apm/deploy/README.md`** 为准(用户/团队维护)。文档有问题时**不得**猜着部署或绕过文档擅自执行生产级危险操作。
|
|
9
|
-
- **参数从哪来**:用户**只需提供需求 ID**(`requirementId
|
|
9
|
+
- **参数从哪来**:用户**只需提供需求 ID**(`requirementId`);**目标部署环境**(如 `test` / `online`)从**当前任务说明/指令**中解析(平台自动部署节点会在指令中写明「部署到 XX 环境」);其余上下文从 **`.apm/workitems/<requirementId>/`** 内按需 **Read**,**不向用户追问**分支等 README 未写明的部署参数。
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -34,24 +34,25 @@ description: 路径相对仓库根 `<repoRoot>`;`.apm` 常被 gitignore,须
|
|
|
34
34
|
|
|
35
35
|
## 输入与前置
|
|
36
36
|
|
|
37
|
-
| 字段
|
|
38
|
-
|
|
|
39
|
-
| **`requirementId`**
|
|
40
|
-
| **`<repoRoot>`**
|
|
41
|
-
|
|
|
42
|
-
|
|
|
37
|
+
| 字段 | 规则 |
|
|
38
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| **`requirementId`** | **必填**。与 **`<repoRoot>/.apm/workitems/<requirementId>/`** 目录名一致;**缺则索要,不猜测**。 |
|
|
40
|
+
| **`<repoRoot>`** | 见上节「路径基准」;所有 `.apm` 路径均锚定于此。 |
|
|
41
|
+
| **`deployEnv`(目标部署环境)** | **从当前任务说明/指令解析**(例如「将项目部署到 online 环境」→ `deployEnv=online`)。**禁止**从 **`requirement-status.yaml`** 读取 `env`——该文件由 `apm pull` 生成,**不含**部署环境字段。若任务说明未给出目标环境:**终止**,说明「任务说明未指定部署环境」。 |
|
|
42
|
+
| **工作项上下文** | 路径 **`<repoRoot>/.apm/workitems/<requirementId>/`**。README 需要何种信息(命名空间、标签、产物说明等),**优先在该目录下找对应文件**;**`requirement-status.yaml`** 仅含 `id` / `status` / `title`,用于核对需求身份,**不作为部署环境来源**。若 README 指向其它文件名(如任务状态、发布说明),在同一目录 **Read** 即可。 |
|
|
43
|
+
| **参数仍不足** | 若 README 要求某信息且在工作项目录、任务说明与 README 附属文档中**仍无法取得**:**不得**向用户口头凑参数;**终止**并列出「README 要什么 / 已查了哪些路径 / 缺什么」。 |
|
|
43
44
|
|
|
44
45
|
---
|
|
45
46
|
|
|
46
47
|
## 流程总览
|
|
47
48
|
|
|
48
|
-
| 序号 | 步骤
|
|
49
|
-
|
|
|
50
|
-
| 1
|
|
51
|
-
| 2
|
|
52
|
-
| 3
|
|
53
|
-
| 4
|
|
54
|
-
| 5
|
|
49
|
+
| 序号 | 步骤 | 说明 |
|
|
50
|
+
| ---- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| 1 | **阅读部署文档** | **Read** 全文:`.apm/deploy/README.md`(及 README 明确要求阅读的附属文档)。 |
|
|
52
|
+
| 2 | **文档验收(门禁)** | 判定 README 是否足以执行;不足则**终止**(见「文档验收」)。 |
|
|
53
|
+
| 3 | **解析部署环境并加载工作项上下文** | 门禁通过后:从**任务说明**解析 `deployEnv`;按 README 所需 **Read** `.apm/workitems/<requirementId>/` 下其它文件;仍缺参数则**终止**(见「输入与前置」)。 |
|
|
54
|
+
| 4 | **按文档执行** | **严格**按 README 的步骤、顺序与命令执行,并用上一步解析出的参数(含 `deployEnv`、替换占位符、导出环境变量);不添加 README 未要求的步骤。 |
|
|
55
|
+
| 5 | **对用户回复** | 用**Markdown 表格**汇总各步与命令结果(见文末模板);简述阻塞或跳过原因。 |
|
|
55
56
|
|
|
56
57
|
---
|
|
57
58
|
|
|
@@ -67,12 +68,12 @@ description: 路径相对仓库根 `<repoRoot>`;`.apm` 常被 gitignore,须
|
|
|
67
68
|
|
|
68
69
|
在阅读完成后、加载工作项上下文与执行任何部署命令前,必须自检下列项。**任一条不满足**:**不得执行部署**;在对话中**逐条列出问题**(引用 README 片段或章节标题即可),然后**终止本技能流程**。
|
|
69
70
|
|
|
70
|
-
| 验收项
|
|
71
|
-
|
|
|
71
|
+
| 验收项 | 不满足时的表现(示例) |
|
|
72
|
+
| ------------ | --------------------------------------------------------------------------------------- |
|
|
72
73
|
| **可执行性** | 缺少前置条件(账号、CLI、密钥、目标环境)、缺少具体命令或顺序混乱到无法唯一确定下一步。 |
|
|
73
|
-
| **一致性**
|
|
74
|
-
| **完整性**
|
|
75
|
-
| **清晰性**
|
|
74
|
+
| **一致性** | 前后矛盾(例如同一环境两套冲突命令)、版本/工具要求互相打架。 |
|
|
75
|
+
| **完整性** | 引用不存在的脚本/配置文件、Broken link、关键占位符未替换说明。 |
|
|
76
|
+
| **清晰性** | 读完仍**不知道**该如何在本仓库完成一次部署(步骤含糊到无法落地)。 |
|
|
76
77
|
|
|
77
78
|
**特别约定(用户维护文档)**
|
|
78
79
|
|
|
@@ -82,13 +83,14 @@ description: 路径相对仓库根 `<repoRoot>`;`.apm` 常被 gitignore,须
|
|
|
82
83
|
|
|
83
84
|
---
|
|
84
85
|
|
|
85
|
-
## 步骤 3
|
|
86
|
+
## 步骤 3:解析部署环境并加载工作项上下文
|
|
86
87
|
|
|
87
88
|
仅在「文档验收」**全部通过**后执行。
|
|
88
89
|
|
|
89
|
-
1.
|
|
90
|
-
2.
|
|
91
|
-
3.
|
|
90
|
+
1. **解析 `deployEnv`**:从**当前任务说明/指令**提取目标部署环境(如 `test`、`online`)。平台自动部署节点典型表述为「将项目部署到 {环境} 环境」。**不得**打开 `requirement-status.yaml` 找 `env` 字段。
|
|
91
|
+
2. 确认目录 **`<repoRoot>/.apm/workitems/<requirementId>/`**(工具相对路径:`.apm/workitems/<requirementId>/`)存在;不存在则**终止**(不要假想 ID)。
|
|
92
|
+
3. 根据 README 描述的占位符或变量,**Read** 该目录内相应文件(路径锚定 `<repoRoot>`);**`requirement-status.yaml`** 可用于核对 `id` / `status` / `title`,**不含**部署环境。
|
|
93
|
+
4. 若 README 需要的某项信息在任务说明、工作项目录与 README 附属文档中**不存在或未填写**:**终止**,说明缺失字段与已查路径;**不**请用户在对话里补充(用户约定仅提供需求 ID 与任务说明中的环境)。
|
|
92
94
|
|
|
93
95
|
---
|
|
94
96
|
|
|
@@ -96,7 +98,7 @@ description: 路径相对仓库根 `<repoRoot>`;`.apm` 常被 gitignore,须
|
|
|
96
98
|
|
|
97
99
|
仅在「工作项上下文」**已满足 README 对参数的要求**后执行。
|
|
98
100
|
|
|
99
|
-
1. **严格对照** README:顺序、工作目录(若文档指定 `cd`)、环境变量、所用 CLI(如 `rush`、`docker`、`kubectl` 等)均以文档为准;将步骤 3 得到的 **`
|
|
101
|
+
1. **严格对照** README:顺序、工作目录(若文档指定 `cd`)、环境变量、所用 CLI(如 `rush`、`docker`、`kubectl`、`apm deploy-frontend` 等)均以文档为准;将步骤 3 得到的 **`deployEnv` 等**按 README 约定注入命令或环境(禁止凭猜测填值)。**默认在 `<repoRoot>` 执行**;遵守本仓库 **AGENTS.md**(如 Rush 安装/构建约定),但**不**执行 README **未写出**的额外构建/发布步骤。
|
|
100
102
|
2. **每条命令**记录:是否成功、退出码或关键输出摘要、失败时的 stderr(可截断至可读长度)。
|
|
101
103
|
3. **失败处理**:与 **Guardrails** 一致——默认**失败即终止**后续部署步骤,在表格与摘要中写明失败命令与原因;**不因网络抖动等做无文档依据的多轮重试**(除非 README **明确**要求重试策略)。
|
|
102
104
|
4. **安全**:若 README 要求确认交互(如 `Are you sure?`),按文档处理;文档要求人工审批而 Agent 无法完成时,执行到该步即停止,并标明「需人工完成」。
|
|
@@ -107,13 +109,13 @@ description: 路径相对仓库根 `<repoRoot>`;`.apm` 常被 gitignore,须
|
|
|
107
109
|
|
|
108
110
|
回复中**必须包含**一张汇总表,建议结构:
|
|
109
111
|
|
|
110
|
-
| 阶段
|
|
111
|
-
|
|
|
112
|
-
| 阅读
|
|
113
|
-
| 验收
|
|
114
|
-
| 上下文 | `.apm/workitems/<requirementId
|
|
115
|
-
| 执行
|
|
116
|
-
| 结论
|
|
112
|
+
| 阶段 | 内容 | 结果 |
|
|
113
|
+
| ------ | ------------------------------------------------------------ | ------------------------------------- |
|
|
114
|
+
| 阅读 | `.apm/deploy/README.md`(及 README 要求阅读的附属文档) | 成功 / 失败(原因) |
|
|
115
|
+
| 验收 | 文档是否足以执行 | 通过 / **未通过**(列具体问题) |
|
|
116
|
+
| 上下文 | 任务说明中的 `deployEnv` + `.apm/workitems/<requirementId>/` | 成功 / **失败**(缺环境或未找到文件) |
|
|
117
|
+
| 执行 | 按 README 执行的命令或步骤摘要(可按行拆分) | 每步 成功 / 失败 / 跳过 / 需人工 |
|
|
118
|
+
| 结论 | 部署是否完成 README 所述目标 | 是 / 否 / 未执行(门禁或上下文未过) |
|
|
117
119
|
|
|
118
120
|
若门禁或上下文未通过:**不要**出现「已尝试部署」的误导性表述;表格中「执行」阶段填 **未执行(原因)**。
|
|
119
121
|
|
|
@@ -126,7 +128,8 @@ description: 路径相对仓库根 `<repoRoot>`;`.apm` 常被 gitignore,须
|
|
|
126
128
|
- **gitignore 不等于不存在**:`.apm` 下文件可能不入 Git 索引;**禁止**用「搜索无结果」代替 **Read** 或磁盘校验。
|
|
127
129
|
- **路径锚定 `<repoRoot>`**:凡 `.apm/deploy`、`.apm/workitems` 等路径**仅**相对仓库根;**禁止**因当前焦点在 monorepo 内某一子目录而在错误基底上拼路径。
|
|
128
130
|
- **文档优先**:无合格 `.apm/deploy/README.md` 解读结果,**不部署**。
|
|
129
|
-
-
|
|
131
|
+
- **部署环境来源**:`deployEnv` **只**来自当前任务说明/指令;**禁止**从 `requirement-status.yaml` 读取 `env`(该字段已不再由 `apm pull` 写入)。
|
|
132
|
+
- **用户只给 ID + 任务环境**:除 **`requirementId`** 与任务说明中的**目标环境**外,**不依赖**用户在对话中口头补充密钥、分支等;这些信息须来自 **工作项目录** 或 README 已写明的本地/CI 约定。缺数据则**终止并写明缺口**,而非让用户「现场补一句」。
|
|
130
133
|
- **失败即终止**:在文档与上下文明确的前提下,任一步骤失败 → 停止后续步骤,仅记录状态;**例外**仅允许 Agent **自身**错误(如错目录)纠正后对**同一步**再试一次,仍失败则终止。
|
|
131
|
-
- **不臆造**:README
|
|
134
|
+
- **不臆造**:README 未写的命令、环境、目标集群/命名空间,**不补充**;任务说明与工作项 **未提供**的字段**不得**编造。
|
|
132
135
|
- **不报假成功**:命令失败或未完成 README 目标时,结论必须为「否」或等价表述。
|
|
@@ -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 的「待确认」或「假设」,避免阻塞整篇文档。
|