ai-project-manage-cli 4.0.22 → 4.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +250 -157
- package/package.json +1 -1
- package/template/theater-skills/apm-theater-dev/SKILL.md +53 -78
package/dist/index.js
CHANGED
|
@@ -177,6 +177,10 @@ var requestConfig = {
|
|
|
177
177
|
})
|
|
178
178
|
},
|
|
179
179
|
theater: {
|
|
180
|
+
sessionBranchBaseline: defineEndpoint({
|
|
181
|
+
method: "GET",
|
|
182
|
+
path: "/theater/sessions/branch-baseline"
|
|
183
|
+
}),
|
|
180
184
|
reportMemberAgentProgress: defineEndpoint({
|
|
181
185
|
method: "POST",
|
|
182
186
|
path: "/theater/sessions/report-member-agent-progress"
|
|
@@ -228,9 +232,16 @@ function requirementWorkitemsDir(requirementId, workspaceDir = WORKSPACE_APM_DIR
|
|
|
228
232
|
function theaterSessionDir(sessionId, apmRoot = WORKSPACE_APM_DIR) {
|
|
229
233
|
return join2(apmRoot, "theater-sessions", sessionId);
|
|
230
234
|
}
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
|
|
235
|
+
function normalizeTheaterArtifactFileName(fileName) {
|
|
236
|
+
return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/^docs\//, "");
|
|
237
|
+
}
|
|
238
|
+
function theaterArtifactLocalRelativePath(fileName) {
|
|
239
|
+
const base = normalizeTheaterArtifactFileName(fileName);
|
|
240
|
+
return `docs/${base}`;
|
|
241
|
+
}
|
|
242
|
+
function theaterSessionDocumentPath(sessionId, fileName = "prd.md", apmRoot = WORKSPACE_APM_DIR) {
|
|
243
|
+
const localRelative = theaterArtifactLocalRelativePath(fileName);
|
|
244
|
+
return join2(theaterSessionDir(sessionId, apmRoot), localRelative);
|
|
234
245
|
}
|
|
235
246
|
async function ensureLoggedConfig() {
|
|
236
247
|
const cfg = await ensureApmConfig();
|
|
@@ -813,7 +824,7 @@ async function runPull(requirementId, workspaceDir) {
|
|
|
813
824
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
814
825
|
import { dirname as dirname2, join as join6 } from "path";
|
|
815
826
|
function normalizeRelativePath(fileName) {
|
|
816
|
-
return fileName
|
|
827
|
+
return theaterArtifactLocalRelativePath(fileName);
|
|
817
828
|
}
|
|
818
829
|
async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
|
|
819
830
|
const trimmedSessionId = sessionId.trim();
|
|
@@ -824,6 +835,7 @@ async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
|
|
|
824
835
|
const api = createApmApiClient(cfg);
|
|
825
836
|
const sessionDir = theaterSessionDir(trimmedSessionId, apmRoot);
|
|
826
837
|
await ensureDirExists(sessionDir);
|
|
838
|
+
await ensureDirExists(join6(sessionDir, "docs"));
|
|
827
839
|
const pageSize = 500;
|
|
828
840
|
let page = 1;
|
|
829
841
|
const items = [];
|
|
@@ -851,6 +863,200 @@ async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
|
|
|
851
863
|
return sessionDir;
|
|
852
864
|
}
|
|
853
865
|
|
|
866
|
+
// src/commands/branch.ts
|
|
867
|
+
import { execFile } from "child_process";
|
|
868
|
+
import { resolve as resolve3 } from "path";
|
|
869
|
+
import { promisify } from "util";
|
|
870
|
+
var execFileAsync = promisify(execFile);
|
|
871
|
+
async function fetchBaselineBranchFromApi(requirementId, cwd) {
|
|
872
|
+
const cfg = await ensureLoggedConfig();
|
|
873
|
+
const api = createApmApiClient(cfg);
|
|
874
|
+
const workdirPath = resolve3(cwd);
|
|
875
|
+
const res = await api.cliRequirements.branchBaseline({
|
|
876
|
+
requirementId,
|
|
877
|
+
workdirPath
|
|
878
|
+
});
|
|
879
|
+
if (!res.repositoryId) {
|
|
880
|
+
throw new Error(
|
|
881
|
+
`[apm] \u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}
|
|
882
|
+
\u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
const name = (res.defaultBranch ?? "").trim();
|
|
886
|
+
if (!name) {
|
|
887
|
+
throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
|
|
888
|
+
}
|
|
889
|
+
return name;
|
|
890
|
+
}
|
|
891
|
+
async function fetchTheaterBaselineBranchFromApi(sessionId, cwd) {
|
|
892
|
+
const cfg = await ensureLoggedConfig();
|
|
893
|
+
const api = createApmApiClient(cfg);
|
|
894
|
+
const workdirPath = resolve3(cwd);
|
|
895
|
+
const res = await api.theater.sessionBranchBaseline({
|
|
896
|
+
sessionId,
|
|
897
|
+
workdirPath
|
|
898
|
+
});
|
|
899
|
+
if (!res.repositoryId) {
|
|
900
|
+
throw new Error(
|
|
901
|
+
`[apm] \u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}
|
|
902
|
+
\u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
const name = (res.defaultBranch ?? "").trim();
|
|
906
|
+
if (!name) {
|
|
907
|
+
throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
|
|
908
|
+
}
|
|
909
|
+
return name;
|
|
910
|
+
}
|
|
911
|
+
function branchNameForRequirement(requirementId) {
|
|
912
|
+
const id = requirementId.trim();
|
|
913
|
+
if (!id) {
|
|
914
|
+
throw new Error("[apm] \u9700\u6C42 ID \u4E0D\u80FD\u4E3A\u7A7A");
|
|
915
|
+
}
|
|
916
|
+
if (/[\s/\\]/.test(id)) {
|
|
917
|
+
throw new Error(
|
|
918
|
+
"[apm] \u9700\u6C42 ID \u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u6216\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BCD\u3001\u6570\u5B57\u3001._- \u7B49"
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
return `feat/req-${id}`;
|
|
922
|
+
}
|
|
923
|
+
var branchNameForTheaterSession = branchNameForRequirement;
|
|
924
|
+
async function execGit(cwd, args, quiet) {
|
|
925
|
+
try {
|
|
926
|
+
const { stdout, stderr } = await execFileAsync("git", args, {
|
|
927
|
+
cwd,
|
|
928
|
+
encoding: "utf8",
|
|
929
|
+
maxBuffer: 10 * 1024 * 1024
|
|
930
|
+
});
|
|
931
|
+
if (!quiet && stderr.trim()) {
|
|
932
|
+
process.stderr.write(stderr);
|
|
933
|
+
}
|
|
934
|
+
return stdout;
|
|
935
|
+
} catch (err) {
|
|
936
|
+
const e = err;
|
|
937
|
+
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
938
|
+
throw new Error(
|
|
939
|
+
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
async function ensureGitRepo(cwd) {
|
|
944
|
+
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
945
|
+
}
|
|
946
|
+
async function getCurrentBranch(cwd) {
|
|
947
|
+
const name = (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
948
|
+
return name;
|
|
949
|
+
}
|
|
950
|
+
async function isWorkingTreeDirty(cwd) {
|
|
951
|
+
const out = await execGit(cwd, ["status", "--porcelain"], true);
|
|
952
|
+
return out.trim().length > 0;
|
|
953
|
+
}
|
|
954
|
+
async function remoteHeadBranchExists(cwd, branch) {
|
|
955
|
+
const out = await execGit(
|
|
956
|
+
cwd,
|
|
957
|
+
["ls-remote", "--heads", "origin", branch],
|
|
958
|
+
true
|
|
959
|
+
);
|
|
960
|
+
return out.trim().length > 0;
|
|
961
|
+
}
|
|
962
|
+
async function localBranchExists(cwd, branch) {
|
|
963
|
+
try {
|
|
964
|
+
await execGit(
|
|
965
|
+
cwd,
|
|
966
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
967
|
+
true
|
|
968
|
+
);
|
|
969
|
+
return true;
|
|
970
|
+
} catch {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
function theaterSessionCommitMessage(sessionId, memberKey) {
|
|
975
|
+
const id = sessionId.trim();
|
|
976
|
+
const key = memberKey?.trim();
|
|
977
|
+
return key ? `feat(req-${id}): ${key} theater agent` : `feat(req-${id}): theater agent`;
|
|
978
|
+
}
|
|
979
|
+
async function commitWorkingTreeIfDirty(cwd, message) {
|
|
980
|
+
await ensureGitRepo(cwd);
|
|
981
|
+
if (!await isWorkingTreeDirty(cwd)) {
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
|
|
985
|
+
await execGit(cwd, ["add", "-A"]);
|
|
986
|
+
await execGit(cwd, ["commit", "-m", commitMessage]);
|
|
987
|
+
console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
|
|
988
|
+
return true;
|
|
989
|
+
}
|
|
990
|
+
async function ensureFeatureBranch(branch, options, fetchBaseline) {
|
|
991
|
+
const cwd = options.cwd ?? process.cwd();
|
|
992
|
+
const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
|
|
993
|
+
await ensureGitRepo(cwd);
|
|
994
|
+
const current = await getCurrentBranch(cwd);
|
|
995
|
+
const dirty = await isWorkingTreeDirty(cwd);
|
|
996
|
+
if (dirty) {
|
|
997
|
+
if (current === branch) {
|
|
998
|
+
await commitWorkingTreeIfDirty(cwd, commitMessage);
|
|
999
|
+
} else {
|
|
1000
|
+
await execGit(cwd, [
|
|
1001
|
+
"stash",
|
|
1002
|
+
"push",
|
|
1003
|
+
"-u",
|
|
1004
|
+
"-m",
|
|
1005
|
+
`apm: switch to ${branch}`
|
|
1006
|
+
]);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
const remoteExists = await remoteHeadBranchExists(cwd, branch);
|
|
1010
|
+
if (remoteExists) {
|
|
1011
|
+
await execGit(cwd, ["fetch", "origin", branch]);
|
|
1012
|
+
const hasLocal = await localBranchExists(cwd, branch);
|
|
1013
|
+
if (hasLocal) {
|
|
1014
|
+
await execGit(cwd, ["checkout", branch]);
|
|
1015
|
+
await execGit(cwd, ["pull", "--no-edit"]);
|
|
1016
|
+
} else {
|
|
1017
|
+
await execGit(cwd, ["checkout", "-b", branch, `origin/${branch}`]);
|
|
1018
|
+
}
|
|
1019
|
+
} else {
|
|
1020
|
+
const onBranch = await getCurrentBranch(cwd) === branch;
|
|
1021
|
+
if (!onBranch) {
|
|
1022
|
+
const hasLocal = await localBranchExists(cwd, branch);
|
|
1023
|
+
if (hasLocal) {
|
|
1024
|
+
await execGit(cwd, ["checkout", branch]);
|
|
1025
|
+
} else {
|
|
1026
|
+
const baselineBranch = await fetchBaseline();
|
|
1027
|
+
await execGit(cwd, ["fetch", "origin", baselineBranch]);
|
|
1028
|
+
await execGit(cwd, [
|
|
1029
|
+
"checkout",
|
|
1030
|
+
"-b",
|
|
1031
|
+
branch,
|
|
1032
|
+
`origin/${baselineBranch}`
|
|
1033
|
+
]);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
await execGit(cwd, ["push", "-u", "origin", branch]);
|
|
1037
|
+
}
|
|
1038
|
+
console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
|
|
1039
|
+
return branch;
|
|
1040
|
+
}
|
|
1041
|
+
async function runBranch(requirementId, options = {}) {
|
|
1042
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1043
|
+
const branch = branchNameForRequirement(requirementId);
|
|
1044
|
+
return ensureFeatureBranch(
|
|
1045
|
+
branch,
|
|
1046
|
+
options,
|
|
1047
|
+
() => fetchBaselineBranchFromApi(requirementId, cwd)
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
async function runTheaterSessionBranch(sessionId, options = {}) {
|
|
1051
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1052
|
+
const branch = branchNameForTheaterSession(sessionId);
|
|
1053
|
+
return ensureFeatureBranch(
|
|
1054
|
+
branch,
|
|
1055
|
+
options,
|
|
1056
|
+
() => fetchTheaterBaselineBranchFromApi(sessionId, cwd)
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
854
1060
|
// src/theater-job-report.ts
|
|
855
1061
|
import { randomUUID } from "crypto";
|
|
856
1062
|
function extractErrorMessage(err) {
|
|
@@ -1074,6 +1280,20 @@ async function handleTheaterJob(api, ws, payload) {
|
|
|
1074
1280
|
});
|
|
1075
1281
|
return;
|
|
1076
1282
|
}
|
|
1283
|
+
try {
|
|
1284
|
+
await runTheaterSessionBranch(sessionId, { cwd: payload.cwd });
|
|
1285
|
+
} catch (branchErr) {
|
|
1286
|
+
logTheaterJobError("\u5207\u6362\u5206\u652F", branchErr, { sessionId, memberKey });
|
|
1287
|
+
await submitTheaterFailure(api, ws, {
|
|
1288
|
+
sessionId,
|
|
1289
|
+
memberKey,
|
|
1290
|
+
logId,
|
|
1291
|
+
agentId,
|
|
1292
|
+
stage: "\u5207\u6362\u5206\u652F",
|
|
1293
|
+
error: extractErrorMessage(branchErr)
|
|
1294
|
+
});
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1077
1297
|
try {
|
|
1078
1298
|
await runPullTheaterSession(sessionId, apmRoot);
|
|
1079
1299
|
} catch (pullErr) {
|
|
@@ -1093,6 +1313,23 @@ async function handleTheaterJob(api, ws, payload) {
|
|
|
1093
1313
|
const agent = await createAgentForJob(payload);
|
|
1094
1314
|
const runResult = await runAgentStream(payload, eventSession, agent);
|
|
1095
1315
|
agentId = runResult.agentId;
|
|
1316
|
+
try {
|
|
1317
|
+
await commitWorkingTreeIfDirty(
|
|
1318
|
+
payload.cwd,
|
|
1319
|
+
theaterSessionCommitMessage(sessionId, memberKey)
|
|
1320
|
+
);
|
|
1321
|
+
} catch (commitErr) {
|
|
1322
|
+
logTheaterJobError("\u63D0\u4EA4\u5DE5\u4F5C\u533A", commitErr, { sessionId, memberKey });
|
|
1323
|
+
await submitTheaterFailure(api, ws, {
|
|
1324
|
+
sessionId,
|
|
1325
|
+
memberKey,
|
|
1326
|
+
logId,
|
|
1327
|
+
agentId,
|
|
1328
|
+
stage: "\u63D0\u4EA4\u5DE5\u4F5C\u533A",
|
|
1329
|
+
error: extractErrorMessage(commitErr)
|
|
1330
|
+
});
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1096
1333
|
const assistantText = eventSession.getAssistantText();
|
|
1097
1334
|
const content = runResult.failed ? runResult.failReason || assistantText || "\u672A\u77E5\u9519\u8BEF" : assistantText || "[Agent \u672A\u4EA7\u751F\u6587\u672C\u8F93\u51FA]";
|
|
1098
1335
|
await submitTheaterResult(api, ws, {
|
|
@@ -1308,150 +1545,6 @@ async function runLogin(opts) {
|
|
|
1308
1545
|
console.log(JSON.stringify({ userId: cfg.userId, baseUrl: cfg.baseUrl }, null, 2));
|
|
1309
1546
|
}
|
|
1310
1547
|
|
|
1311
|
-
// src/commands/branch.ts
|
|
1312
|
-
import { execFile } from "child_process";
|
|
1313
|
-
import { resolve as resolve3 } from "path";
|
|
1314
|
-
import { promisify } from "util";
|
|
1315
|
-
var execFileAsync = promisify(execFile);
|
|
1316
|
-
async function fetchBaselineBranchFromApi(requirementId, cwd) {
|
|
1317
|
-
const cfg = await ensureLoggedConfig();
|
|
1318
|
-
const api = createApmApiClient(cfg);
|
|
1319
|
-
const workdirPath = resolve3(cwd);
|
|
1320
|
-
const res = await api.cliRequirements.branchBaseline({
|
|
1321
|
-
requirementId,
|
|
1322
|
-
workdirPath
|
|
1323
|
-
});
|
|
1324
|
-
if (!res.repositoryId) {
|
|
1325
|
-
throw new Error(
|
|
1326
|
-
`[apm] \u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}
|
|
1327
|
-
\u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`
|
|
1328
|
-
);
|
|
1329
|
-
}
|
|
1330
|
-
const name = (res.defaultBranch ?? "").trim();
|
|
1331
|
-
if (!name) {
|
|
1332
|
-
throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
|
|
1333
|
-
}
|
|
1334
|
-
return name;
|
|
1335
|
-
}
|
|
1336
|
-
function branchNameForRequirement(requirementId) {
|
|
1337
|
-
const id = requirementId.trim();
|
|
1338
|
-
if (!id) {
|
|
1339
|
-
throw new Error("[apm] \u9700\u6C42 ID \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1340
|
-
}
|
|
1341
|
-
if (/[\s/\\]/.test(id)) {
|
|
1342
|
-
throw new Error(
|
|
1343
|
-
"[apm] \u9700\u6C42 ID \u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u6216\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BCD\u3001\u6570\u5B57\u3001._- \u7B49"
|
|
1344
|
-
);
|
|
1345
|
-
}
|
|
1346
|
-
return `feat/req-${id}`;
|
|
1347
|
-
}
|
|
1348
|
-
async function execGit(cwd, args, quiet) {
|
|
1349
|
-
try {
|
|
1350
|
-
const { stdout, stderr } = await execFileAsync("git", args, {
|
|
1351
|
-
cwd,
|
|
1352
|
-
encoding: "utf8",
|
|
1353
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1354
|
-
});
|
|
1355
|
-
if (!quiet && stderr.trim()) {
|
|
1356
|
-
process.stderr.write(stderr);
|
|
1357
|
-
}
|
|
1358
|
-
return stdout;
|
|
1359
|
-
} catch (err) {
|
|
1360
|
-
const e = err;
|
|
1361
|
-
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
1362
|
-
throw new Error(
|
|
1363
|
-
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
1364
|
-
);
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
async function ensureGitRepo(cwd) {
|
|
1368
|
-
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
1369
|
-
}
|
|
1370
|
-
async function getCurrentBranch(cwd) {
|
|
1371
|
-
const name = (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
1372
|
-
return name;
|
|
1373
|
-
}
|
|
1374
|
-
async function isWorkingTreeDirty(cwd) {
|
|
1375
|
-
const out = await execGit(cwd, ["status", "--porcelain"], true);
|
|
1376
|
-
return out.trim().length > 0;
|
|
1377
|
-
}
|
|
1378
|
-
async function remoteHeadBranchExists(cwd, branch) {
|
|
1379
|
-
const out = await execGit(
|
|
1380
|
-
cwd,
|
|
1381
|
-
["ls-remote", "--heads", "origin", branch],
|
|
1382
|
-
true
|
|
1383
|
-
);
|
|
1384
|
-
return out.trim().length > 0;
|
|
1385
|
-
}
|
|
1386
|
-
async function localBranchExists(cwd, branch) {
|
|
1387
|
-
try {
|
|
1388
|
-
await execGit(
|
|
1389
|
-
cwd,
|
|
1390
|
-
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
1391
|
-
true
|
|
1392
|
-
);
|
|
1393
|
-
return true;
|
|
1394
|
-
} catch {
|
|
1395
|
-
return false;
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
async function runBranch(requirementId, options = {}) {
|
|
1399
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1400
|
-
const branch = branchNameForRequirement(requirementId);
|
|
1401
|
-
const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
|
|
1402
|
-
await ensureGitRepo(cwd);
|
|
1403
|
-
const current = await getCurrentBranch(cwd);
|
|
1404
|
-
const dirty = await isWorkingTreeDirty(cwd);
|
|
1405
|
-
if (dirty) {
|
|
1406
|
-
if (current === branch) {
|
|
1407
|
-
await execGit(cwd, ["add", "-A"]);
|
|
1408
|
-
await execGit(cwd, ["commit", "-m", commitMessage]);
|
|
1409
|
-
} else {
|
|
1410
|
-
await execGit(cwd, [
|
|
1411
|
-
"stash",
|
|
1412
|
-
"push",
|
|
1413
|
-
"-u",
|
|
1414
|
-
"-m",
|
|
1415
|
-
`apm: switch to ${branch}`
|
|
1416
|
-
]);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
const remoteExists = await remoteHeadBranchExists(cwd, branch);
|
|
1420
|
-
if (remoteExists) {
|
|
1421
|
-
await execGit(cwd, ["fetch", "origin", branch]);
|
|
1422
|
-
const hasLocal = await localBranchExists(cwd, branch);
|
|
1423
|
-
if (hasLocal) {
|
|
1424
|
-
await execGit(cwd, ["checkout", branch]);
|
|
1425
|
-
await execGit(cwd, ["pull", "--no-edit"]);
|
|
1426
|
-
} else {
|
|
1427
|
-
await execGit(cwd, ["checkout", "-b", branch, `origin/${branch}`]);
|
|
1428
|
-
}
|
|
1429
|
-
} else {
|
|
1430
|
-
const onBranch = await getCurrentBranch(cwd) === branch;
|
|
1431
|
-
if (!onBranch) {
|
|
1432
|
-
const hasLocal = await localBranchExists(cwd, branch);
|
|
1433
|
-
if (hasLocal) {
|
|
1434
|
-
await execGit(cwd, ["checkout", branch]);
|
|
1435
|
-
} else {
|
|
1436
|
-
const baselineBranch = await fetchBaselineBranchFromApi(
|
|
1437
|
-
requirementId,
|
|
1438
|
-
cwd
|
|
1439
|
-
);
|
|
1440
|
-
await execGit(cwd, ["fetch", "origin", baselineBranch]);
|
|
1441
|
-
await execGit(cwd, [
|
|
1442
|
-
"checkout",
|
|
1443
|
-
"-b",
|
|
1444
|
-
branch,
|
|
1445
|
-
`origin/${baselineBranch}`
|
|
1446
|
-
]);
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
await execGit(cwd, ["push", "-u", "origin", branch]);
|
|
1450
|
-
}
|
|
1451
|
-
console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
|
|
1452
|
-
return branch;
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
1548
|
// src/commands/refine.ts
|
|
1456
1549
|
import { readFileSync as readFileSync5 } from "fs";
|
|
1457
1550
|
import { join as join9 } from "path";
|
|
@@ -1612,21 +1705,19 @@ async function runUpdateTheaterSkills() {
|
|
|
1612
1705
|
|
|
1613
1706
|
// src/commands/sync-session-document.ts
|
|
1614
1707
|
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
1615
|
-
var
|
|
1708
|
+
var DEFAULT_FILE = "prd.md";
|
|
1616
1709
|
async function runSyncSessionDocument(sessionId, options) {
|
|
1617
1710
|
const trimmedSessionId = sessionId.trim();
|
|
1618
1711
|
if (!trimmedSessionId) {
|
|
1619
1712
|
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1620
1713
|
process.exit(1);
|
|
1621
1714
|
}
|
|
1622
|
-
const
|
|
1623
|
-
/\\/g,
|
|
1624
|
-
"/"
|
|
1625
|
-
);
|
|
1715
|
+
const fileArg = (options?.file?.trim() || DEFAULT_FILE).replace(/\\/g, "/");
|
|
1626
1716
|
const apmRoot = options?.apmRoot ?? WORKSPACE_APM_DIR;
|
|
1717
|
+
const localRelativePath = theaterArtifactLocalRelativePath(fileArg);
|
|
1627
1718
|
const absPath = theaterSessionDocumentPath(
|
|
1628
1719
|
trimmedSessionId,
|
|
1629
|
-
|
|
1720
|
+
fileArg,
|
|
1630
1721
|
apmRoot
|
|
1631
1722
|
);
|
|
1632
1723
|
if (!existsSync6(absPath)) {
|
|
@@ -1639,10 +1730,12 @@ async function runSyncSessionDocument(sessionId, options) {
|
|
|
1639
1730
|
const api = createApmApiClient(cfg);
|
|
1640
1731
|
const data = await api.theaterSessionArtifact.upsert({
|
|
1641
1732
|
sessionId: trimmedSessionId,
|
|
1642
|
-
fileName:
|
|
1733
|
+
fileName: normalizeTheaterArtifactFileName(fileArg),
|
|
1643
1734
|
content
|
|
1644
1735
|
});
|
|
1645
|
-
console.log(
|
|
1736
|
+
console.log(
|
|
1737
|
+
`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u6587\u6863: ${localRelativePath} \u2192 ${data.fileName} (artifactId=${data.id})`
|
|
1738
|
+
);
|
|
1646
1739
|
}
|
|
1647
1740
|
|
|
1648
1741
|
// src/commands/update-dev-status.ts
|
|
@@ -2738,7 +2831,7 @@ function buildProgram() {
|
|
|
2738
2831
|
"\u5C06 .apm/theater-sessions/<sessionId>/docs/ \u4E0B Markdown \u6587\u6863 upsert \u5230\u5E73\u53F0\u4F1A\u8BDD\u4EA7\u7269\uFF08\u4E0D\u542B\u5BF9\u8BDD\u65E5\u5FD7\uFF09"
|
|
2739
2832
|
).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").option(
|
|
2740
2833
|
"--file <path>",
|
|
2741
|
-
"\
|
|
2834
|
+
"\u6587\u6863\u540D\u6216\u76F8\u5BF9\u8DEF\u5F84\uFF08\u5982 prd.md\u3001docs/backend.md\uFF1B\u672C\u5730\u56FA\u5B9A\u843D\u5728 docs/ \u4E0B\uFF09"
|
|
2742
2835
|
).action(async (sessionId, opts) => {
|
|
2743
2836
|
await runSyncSessionDocument(sessionId, { file: opts.file });
|
|
2744
2837
|
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: apm-theater-dev
|
|
3
|
-
description: 按剧场会话 ID
|
|
3
|
+
description: 按剧场会话 ID 全自动编码:按需读会话 PRD(及 backend/api 文档)、按改动规模选择 Quick 或 Spec、推送代码;子 Agent 承担编码与规划落地;当用户 @ 本技能、提及剧场全自动开发或「apm-theater-dev」时使用。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 剧场全自动开发(按会话 ID)
|
|
7
7
|
|
|
8
8
|
用户给出 **剧场会话 ID**(`sessionId`,与 `.apm/theater-sessions/<sessionId>/` 目录名一致)。**缺 ID 时索要,不猜测。**
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
由 **`THEATER_JOB` / `apm connect`** 自动完成(**无需在本技能中切分支**):
|
|
11
|
+
|
|
12
|
+
- 检出或创建 **`feat/req-<sessionId>`** 并拉取最新
|
|
13
|
+
- **pull** 平台会话文档到 `.apm/theater-sessions/<sessionId>/docs/`
|
|
14
|
+
- Agent 结束后若有未提交变更,CLI 自动 **`git add` & `commit`**
|
|
15
|
+
|
|
16
|
+
Agent **对话日志不上传**。**不要**执行 `apm sync-session-document`(会话文档由网页或其他角色维护)。
|
|
11
17
|
|
|
12
18
|
---
|
|
13
19
|
|
|
@@ -19,60 +25,47 @@ description: 按剧场会话 ID 全自动编码:切分支、读会话 PRD(
|
|
|
19
25
|
| **会话上下文** | 若由 `THEATER_JOB` 触发,`theaterSessionId` 即 `sessionId`;缺省时向用户索要,不猜测。 |
|
|
20
26
|
|
|
21
27
|
**会话根目录**:`.apm/theater-sessions/<sessionId>/`
|
|
22
|
-
**PRD
|
|
23
|
-
|
|
28
|
+
**PRD(按需)**:`docs/prd.md`
|
|
29
|
+
**可选参考(按需)**:`docs/backend.md`、`docs/api.md`
|
|
24
30
|
|
|
25
31
|
---
|
|
26
32
|
|
|
27
33
|
## 技能文件定位(apm-propose / apm-apply-change)
|
|
28
34
|
|
|
29
|
-
Spec 路径依赖的技能**仅**来自 **`.apm/skills
|
|
35
|
+
Spec 路径依赖的技能**仅**来自 **`.apm/skills/`**。进入 Spec 子流程前父 Agent **须 Read**:
|
|
30
36
|
|
|
31
37
|
| 技能 | 路径 |
|
|
32
38
|
| -------------------- | --------------------------------------- |
|
|
33
39
|
| **apm-propose** | `.apm/skills/apm-propose/SKILL.md` |
|
|
34
40
|
| **apm-apply-change** | `.apm/skills/apm-apply-change/SKILL.md` |
|
|
35
41
|
|
|
36
|
-
instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-instruction.md`)。若上述 `SKILL.md` **不存在或不可读**:**不得**进入 Spec 子流程;在表格中标记失败原因(例如需先 `apm init` 或 `apm update-skills`),并停止步骤
|
|
42
|
+
instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-instruction.md`)。若上述 `SKILL.md` **不存在或不可读**:**不得**进入 Spec 子流程;在表格中标记失败原因(例如需先 `apm init` 或 `apm update-skills`),并停止步骤 3。
|
|
37
43
|
|
|
38
|
-
委派子 Agent 时须说明:本轮为**剧场会话**,**`sessionId`** 替代需求 ID;PRD 在 **`docs/prd.md
|
|
44
|
+
委派子 Agent 时须说明:本轮为**剧场会话**,**`sessionId`** 替代需求 ID;PRD 在 **`docs/prd.md`**(按需阅读);Spec 工件落在 **`.apm/theater-sessions/<sessionId>/`**(非 `workitems`)。
|
|
39
45
|
|
|
40
46
|
---
|
|
41
47
|
|
|
42
48
|
## 流程总览
|
|
43
49
|
|
|
44
|
-
| 序号 | 步骤
|
|
45
|
-
| ---- |
|
|
46
|
-
| 1 |
|
|
47
|
-
| 2 |
|
|
48
|
-
| 3 | **
|
|
49
|
-
| 4 |
|
|
50
|
-
| 5 | **提交与推送** | `git` 提交并 `push`,工作区干净 |
|
|
51
|
-
| 6 | **同步会话文档** | 对本轮有改动的 `.md` 逐一 `apm sync-session-document`(仅 `docs/` 等业务文档,不含日志) |
|
|
52
|
-
| 7 | **对用户回复** | **一张 Markdown 表格**汇总各步执行结果(见文末模板) |
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## 步骤 1:切分支
|
|
57
|
-
|
|
58
|
-
1. 在**仓库/工作区根目录**执行(分支名固定为 `feat/theater-<sessionId>`):
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
git fetch origin
|
|
62
|
-
git switch feat/theater-<sessionId> 2>/dev/null || git switch -c feat/theater-<sessionId>
|
|
63
|
-
```
|
|
50
|
+
| 序号 | 步骤 | 说明 |
|
|
51
|
+
| ---- | ------------------------- | ----------------------------------------------------------------------------------------- |
|
|
52
|
+
| 1 | **按需读文档 + 成本评估** | 按本轮任务**按需 Read** `docs/prd.md` 及需要的 `backend.md` / `api.md`,判定 Quick / Spec |
|
|
53
|
+
| 2 | **Quick 开发** | 仅当判定为「改动成本较小」时执行;由 **子 Agent** 写代码 |
|
|
54
|
+
| 3 | **Spec 开发** | 仅当判定为「改动成本较大」时执行;先 **apm-propose** 再 **apm-apply-change**(子 Agent) |
|
|
55
|
+
| 4 | **推送** | `git push`;确认工作区干净(提交通常已由 CLI 在 Agent 结束时完成) |
|
|
64
56
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
2. 记录:是否成功、当前分支名、简要输出或错误(写入最终表格)。**失败则按「Guardrails → 失败即终止」处理**。
|
|
57
|
+
流程**仅以上 4 步**,**无**同步会话文档环节(不执行 `apm sync-session-document`)。
|
|
68
58
|
|
|
69
59
|
---
|
|
70
60
|
|
|
71
|
-
## 步骤
|
|
61
|
+
## 步骤 1:按需读文档并评估改动成本
|
|
72
62
|
|
|
73
|
-
1.
|
|
74
|
-
|
|
75
|
-
|
|
63
|
+
1. **不要**无差别全文 Read `prd.md`;根据主持委派任务、已有对话与实现范围,**按需**阅读:
|
|
64
|
+
- 需要理解需求范围或验收时 → **Read** `docs/prd.md`(全文或相关章节)
|
|
65
|
+
- 涉及后端实现思路 → **Read** `docs/backend.md`
|
|
66
|
+
- 涉及接口联调 → **Read** `docs/api.md`
|
|
67
|
+
2. 若任务明确只需改代码、且上下文已足够,可**跳过**读 PRD,但在表格中注明依据(例如「主持任务已说明改 X 文件」)。
|
|
68
|
+
3. 若判定需要 PRD/backend/api 但文件不存在或读失败,**停止后续实现**,仅在表格中标记失败原因。
|
|
76
69
|
4. **不**为评估向用户发起追问;信息不足时倾向 **Spec**(保守)。
|
|
77
70
|
|
|
78
71
|
### Quick(较小)与 Spec(较大)判定参考
|
|
@@ -81,85 +74,67 @@ instruction 子文件随该目录类推(如 `.apm/skills/apm-propose/propose-i
|
|
|
81
74
|
|
|
82
75
|
- 影响范围局部:少量文件或单一层次(例如仅前端组件、或仅一个后端模块小改)。
|
|
83
76
|
- 无新表结构/大规模迁移/权限模型变更。
|
|
84
|
-
-
|
|
77
|
+
- 验收点清晰且数量少(经验上 **≤3** 条独立验收维度)。
|
|
85
78
|
- 不需要跨多服务的架构裁定即可开工。
|
|
86
79
|
|
|
87
80
|
**倾向 Spec**(命中任一条即可):
|
|
88
81
|
|
|
89
82
|
- 前后端联动、多包改造或新公共抽象。
|
|
90
83
|
- 新数据模型、迁移、或安全/审计/权限相关。
|
|
91
|
-
-
|
|
84
|
+
- 范围大、条款多,或存在明显「待确认/多方案」需先规划。
|
|
92
85
|
- 评估认为不先产出 **proposal / design / specs / tasks** 则难以保证实现与验收对齐。
|
|
93
86
|
|
|
94
|
-
在表格「步骤
|
|
87
|
+
在表格「步骤 1」中写明结论:**Quick** 或 **Spec**,以及**一行内**理由(关键词即可);若读了哪些文档也一并注明。
|
|
95
88
|
|
|
96
89
|
---
|
|
97
90
|
|
|
98
|
-
## 步骤
|
|
91
|
+
## 步骤 2:Quick 开发模式(子 Agent)
|
|
99
92
|
|
|
100
|
-
**条件**:步骤
|
|
93
|
+
**条件**:步骤 1 判定为 **Quick**。
|
|
101
94
|
|
|
102
|
-
1. 父 Agent
|
|
95
|
+
1. 父 Agent 已按需掌握必要文档;委派时在提示中写明 **`sessionId`**、会话根路径、以及「实现须对照已读文档与主持任务,改动范围最小化」。
|
|
103
96
|
2. 使用 **Task** 工具,`subagent_type: generalPurpose`,**readonly: false**,委派子 Agent:
|
|
104
|
-
-
|
|
105
|
-
-
|
|
97
|
+
- **按需 Read** `docs/prd.md` 及需要的 `backend.md` / `api.md`。
|
|
98
|
+
- 按任务与文档直接改代码;遵守本仓库构建与依赖约定(AGENTS.md)。
|
|
106
99
|
- 完成后在返回中说明:改了哪些路径、如何对照验收、是否通过本地可执行的检查(若跑了 `rushx build` / 测试等则写明结果)。
|
|
107
|
-
3. 父 Agent 根据子 Agent 返回在表格中填写步骤
|
|
100
|
+
3. 父 Agent 根据子 Agent 返回在表格中填写步骤 2 **状态**;本模式下步骤 3 填 **跳过**。
|
|
108
101
|
|
|
109
102
|
---
|
|
110
103
|
|
|
111
|
-
## 步骤
|
|
104
|
+
## 步骤 3:Spec 开发模式(子 Agent)
|
|
112
105
|
|
|
113
|
-
**条件**:步骤
|
|
106
|
+
**条件**:步骤 1 判定为 **Spec**。
|
|
114
107
|
|
|
115
108
|
1. 父 Agent **Read** **apm-propose**、**apm-apply-change** 的 `SKILL.md`(**仅** `.apm/skills/` 下路径,见上节)。
|
|
116
|
-
2. **子 Agent A(规划)**:Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-propose/SKILL.md` 并遵循,但将工件目录改为 **`.apm/theater-sessions/<sessionId>/`**,PRD 来源为 **`docs/prd.md
|
|
109
|
+
2. **子 Agent A(规划)**:Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-propose/SKILL.md` 并遵循,但将工件目录改为 **`.apm/theater-sessions/<sessionId>/`**,PRD 来源为 **`docs/prd.md`**(按需阅读),上下文使用 **`sessionId`** 而非 `requirementId`;生成 **proposal、design、specs、tasks** 等(顺序以 SKILL 为准)。
|
|
117
110
|
3. **子 Agent B(实现)**:待 A 成功落盘后,再 Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-apply-change/SKILL.md` 并遵循:按 **`.apm/theater-sessions/<sessionId>/tasks.md`** 驱动实现与勾选。
|
|
118
111
|
4. 若未产出可用 **`tasks.md`**,不得强行进入 **apm-apply-change**;表格中标记阻塞原因。
|
|
119
|
-
5. 表格中步骤
|
|
112
|
+
5. 表格中步骤 2 填 **跳过**;步骤 3 分两行或合并一行写清 propose / apply 状态。
|
|
120
113
|
|
|
121
114
|
---
|
|
122
115
|
|
|
123
|
-
## 步骤
|
|
116
|
+
## 步骤 4:推送
|
|
124
117
|
|
|
125
118
|
在仓库根目录执行:
|
|
126
119
|
|
|
127
|
-
1. `git status
|
|
128
|
-
2.
|
|
129
|
-
3. `git
|
|
130
|
-
4. 再次 `git status`:**须为干净工作区**。
|
|
131
|
-
|
|
132
|
-
将命令结果、最终分支名、是否已 push、工作区是否干净写入表格。
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## 步骤 6:同步会话文档
|
|
137
|
-
|
|
138
|
-
对本轮**有内容变更**的 Markdown(通常在 `docs/`,Spec 模式可能含会话根目录下的 `proposal.md` 等),在仓库根目录逐一执行:
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
apm sync-session-document <sessionId> --file docs/prd.md
|
|
142
|
-
apm sync-session-document <sessionId> --file <其他相对路径>
|
|
143
|
-
```
|
|
120
|
+
1. `git status`:确认变更范围;若仍有未提交变更(例如子 Agent 在 CLI 自动 commit 之后又改了文件),执行 `git add -A` 与 `git commit -m "feat(req-<sessionId>): <简短说明>"`。
|
|
121
|
+
2. `git push`:推送到当前分支 **`feat/req-<sessionId>`**(首次必要时 `-u origin feat/req-<sessionId>`)。
|
|
122
|
+
3. 再次 `git status`:**须为干净工作区**。
|
|
144
123
|
|
|
145
|
-
|
|
146
|
-
- 若本轮未改动任何需上平台的文档,表格步骤 6 填 **跳过**。
|
|
147
|
-
- 同步失败时如实记录,不假装成功。
|
|
124
|
+
将命令结果、当前分支名、是否已 push、工作区是否干净写入表格(见下节「回复格式」)。
|
|
148
125
|
|
|
149
126
|
---
|
|
150
127
|
|
|
151
|
-
##
|
|
128
|
+
## 回复格式
|
|
152
129
|
|
|
153
|
-
|
|
130
|
+
完成步骤 4 后,对用户回复 **必须包含一张 Markdown 表格**,汇总 **步骤 1 ~ 4**:
|
|
154
131
|
|
|
155
132
|
| 步骤 | 内容 | 结果 |
|
|
156
133
|
| ---- | ------------------------------------------------ | ------------------------------ |
|
|
157
|
-
| 1 |
|
|
158
|
-
| 2 |
|
|
159
|
-
| 3 |
|
|
160
|
-
| 4 |
|
|
161
|
-
| 5 | commit & push;工作区干净 | 成功 / 失败(原因) |
|
|
162
|
-
| 6 | `sync-session-document`(有改动的文档) | 成功 / 失败 / **跳过** |
|
|
134
|
+
| 1 | 按需读文档 + 成本评估 | Quick 或 Spec;读了哪些 / 失败 |
|
|
135
|
+
| 2 | Quick 开发(子 Agent) | 成功 / 失败 / **跳过** |
|
|
136
|
+
| 3 | Spec:apm-propose → apm-apply-change(子 Agent) | 成功 / 失败 / **跳过** |
|
|
137
|
+
| 4 | push;工作区干净 | 成功 / 失败(原因) |
|
|
163
138
|
|
|
164
139
|
**可选**:表格外增加**简短**摘要(当前分支、阻塞点)。
|
|
165
140
|
|
|
@@ -167,8 +142,8 @@ apm sync-session-document <sessionId> --file <其他相对路径>
|
|
|
167
142
|
|
|
168
143
|
## Guardrails
|
|
169
144
|
|
|
170
|
-
- **失败即终止**:在用户提供的 **`sessionId`**
|
|
145
|
+
- **失败即终止**:在用户提供的 **`sessionId`** 等参数合法、命令与路径按本技能书写的前提下,任一步骤(含读文件、子 Agent、`git`)**一旦失败**:**停止后续所有步骤**,仅在表格中记录失败步骤与原因;**不要**为登录、依赖、网络等做**多次**重试。
|
|
171
146
|
- **例外(Agent 自身失误)**:若失败明显由执行 Agent **用错工作目录、读错/漏写路径** 等导致,**允许**纠正后**仅对该失败步骤再执行一次**;纠正后仍失败则**立即终止**。
|
|
172
|
-
-
|
|
147
|
+
- **不要**在无依据的情况下编造需求实现;需要 PRD 时须先 Read,读不到则停止。
|
|
173
148
|
- **不要**上传或同步 Agent 对话日志。
|
|
174
149
|
- **子 Agent** 提示中须带 **`sessionId`** 与会话根路径,避免改错工作树。
|