ai-project-manage-cli 6.0.42 → 6.0.44
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
|
@@ -127,6 +127,13 @@ function resolveWorkdirPath(cwd = process.cwd()) {
|
|
|
127
127
|
return normalizeWorkdirPath(absolute);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
+
function requireRemoteWorkdir(workdir) {
|
|
131
|
+
const trimmed = typeof workdir === "string" ? workdir.trim() : "";
|
|
132
|
+
if (!trimmed) {
|
|
133
|
+
throw new Error("[apm] \u8FDC\u7A0B\u6D88\u606F\u7F3A\u5C11\u5DE5\u4F5C\u76EE\u5F55 workdir");
|
|
134
|
+
}
|
|
135
|
+
return resolveWorkdirPath(trimmed);
|
|
136
|
+
}
|
|
130
137
|
|
|
131
138
|
// src/command-utils.ts
|
|
132
139
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -611,7 +618,7 @@ async function runBranch(sessionId, options = {}) {
|
|
|
611
618
|
|
|
612
619
|
// src/commands/pull.ts
|
|
613
620
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
614
|
-
import {
|
|
621
|
+
import { join as join7 } from "path";
|
|
615
622
|
import { stringify as yamlStringify } from "yaml";
|
|
616
623
|
|
|
617
624
|
// src/session-messages-xml.ts
|
|
@@ -908,13 +915,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
908
915
|
}
|
|
909
916
|
|
|
910
917
|
// src/commands/pull.ts
|
|
911
|
-
function
|
|
912
|
-
if (apmRoot) {
|
|
913
|
-
return resolveWorkdirPath(dirname2(apmRoot));
|
|
914
|
-
}
|
|
915
|
-
return resolveWorkdirPath();
|
|
916
|
-
}
|
|
917
|
-
async function runPull(sessionId, apmRoot) {
|
|
918
|
+
async function runPull(sessionId, remoteWorkdir) {
|
|
918
919
|
const trimmedId = sessionId.trim();
|
|
919
920
|
if (!trimmedId) {
|
|
920
921
|
console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
@@ -922,6 +923,8 @@ async function runPull(sessionId, apmRoot) {
|
|
|
922
923
|
}
|
|
923
924
|
const cfg = await ensureLoggedConfig();
|
|
924
925
|
const api = createApmApiClient(cfg);
|
|
926
|
+
const workdir = remoteWorkdir === void 0 ? resolveWorkdirPath() : requireRemoteWorkdir(remoteWorkdir);
|
|
927
|
+
const apmRoot = workspaceApmDir(workdir);
|
|
925
928
|
const [detail, members, documents, attachments, messages] = await Promise.all(
|
|
926
929
|
[
|
|
927
930
|
api.cli.sessionDetail({ sessionId: trimmedId }),
|
|
@@ -976,8 +979,7 @@ async function runPull(sessionId, apmRoot) {
|
|
|
976
979
|
"utf8"
|
|
977
980
|
);
|
|
978
981
|
await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
|
|
979
|
-
|
|
980
|
-
await syncPlatformRules(cfg, trimmedId, workdirPath, apmRoot);
|
|
982
|
+
await syncPlatformRules(cfg, trimmedId, workdir, apmRoot);
|
|
981
983
|
console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
|
|
982
984
|
return dir;
|
|
983
985
|
}
|
|
@@ -987,12 +989,12 @@ import { spawnSync } from "child_process";
|
|
|
987
989
|
|
|
988
990
|
// src/version.ts
|
|
989
991
|
import { readFileSync as readFileSync5 } from "fs";
|
|
990
|
-
import { dirname as
|
|
992
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
991
993
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
992
994
|
var CLI_PACKAGE_NAME = "ai-project-manage-cli";
|
|
993
995
|
function readCliVersion() {
|
|
994
996
|
try {
|
|
995
|
-
const dir =
|
|
997
|
+
const dir = dirname2(fileURLToPath2(import.meta.url));
|
|
996
998
|
const pkgPath = join8(dir, "..", "package.json");
|
|
997
999
|
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
998
1000
|
return pkg.version ?? "0.0.0";
|
|
@@ -1430,7 +1432,6 @@ import {
|
|
|
1430
1432
|
CursorAgentError
|
|
1431
1433
|
} from "@cursor/sdk";
|
|
1432
1434
|
import { setMaxListeners as setMaxListeners2 } from "node:events";
|
|
1433
|
-
import { resolve as resolve4 } from "path";
|
|
1434
1435
|
|
|
1435
1436
|
// src/session-utils.ts
|
|
1436
1437
|
var EventSession = class {
|
|
@@ -1592,7 +1593,7 @@ ${JSON.stringify(event, null, 2)}
|
|
|
1592
1593
|
|
|
1593
1594
|
// src/commands/connect/agent-session-registry.ts
|
|
1594
1595
|
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1595
|
-
import { dirname as
|
|
1596
|
+
import { dirname as dirname3, resolve as resolve3 } from "node:path";
|
|
1596
1597
|
function registryPath(workdir, sessionId) {
|
|
1597
1598
|
return resolve3(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
|
|
1598
1599
|
}
|
|
@@ -1618,7 +1619,7 @@ function readRegistry(path10) {
|
|
|
1618
1619
|
return {};
|
|
1619
1620
|
}
|
|
1620
1621
|
function writeRegistry(path10, registry) {
|
|
1621
|
-
mkdirSync5(
|
|
1622
|
+
mkdirSync5(dirname3(path10), { recursive: true });
|
|
1622
1623
|
writeFileSync7(path10, `${JSON.stringify(registry, null, 2)}
|
|
1623
1624
|
`, "utf8");
|
|
1624
1625
|
}
|
|
@@ -1816,16 +1817,16 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1816
1817
|
if (!apiKey) {
|
|
1817
1818
|
throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
|
|
1818
1819
|
}
|
|
1819
|
-
const
|
|
1820
|
+
const workdir = resolveWorkdirPath(ctx.workdir);
|
|
1820
1821
|
const prompt = ctx.prompt;
|
|
1821
1822
|
console.log(
|
|
1822
|
-
`[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${
|
|
1823
|
+
`[apm] Cursor Agent \u5F00\u59CB messageId=${ctx.messageId} sessionId=${ctx.sessionId} cwd=${workdir}`
|
|
1823
1824
|
);
|
|
1824
1825
|
const { agent, resumed } = await obtainAgent({
|
|
1825
1826
|
apiKey,
|
|
1826
1827
|
model: ctx.model,
|
|
1827
|
-
cwd,
|
|
1828
|
-
workdir
|
|
1828
|
+
cwd: workdir,
|
|
1829
|
+
workdir,
|
|
1829
1830
|
sessionId: ctx.sessionId,
|
|
1830
1831
|
user: ctx.user
|
|
1831
1832
|
});
|
|
@@ -1884,7 +1885,7 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1884
1885
|
});
|
|
1885
1886
|
console.error(`[apm] ${failureMessage}`);
|
|
1886
1887
|
if (resumed) {
|
|
1887
|
-
clearSessionAgentId(
|
|
1888
|
+
clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
|
|
1888
1889
|
}
|
|
1889
1890
|
throw new Error(failureMessage);
|
|
1890
1891
|
}
|
|
@@ -1895,7 +1896,7 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1895
1896
|
} catch (err) {
|
|
1896
1897
|
if (err instanceof CursorAgentError) {
|
|
1897
1898
|
if (resumed) {
|
|
1898
|
-
clearSessionAgentId(
|
|
1899
|
+
clearSessionAgentId(workdir, ctx.sessionId, ctx.user);
|
|
1899
1900
|
}
|
|
1900
1901
|
throw new Error(
|
|
1901
1902
|
`Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
|
|
@@ -1912,23 +1913,27 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1912
1913
|
|
|
1913
1914
|
// src/commands/connect/pre-step-cache.ts
|
|
1914
1915
|
var PULL_TTL_MS = 3e4;
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
function shouldRunBranch(sessionId) {
|
|
1918
|
-
return lastBranchSessionId !== sessionId;
|
|
1916
|
+
function sessionWorkdirKey(sessionId, workdir) {
|
|
1917
|
+
return `${sessionId}\0${workdir}`;
|
|
1919
1918
|
}
|
|
1920
|
-
|
|
1921
|
-
|
|
1919
|
+
var lastBranchKey = null;
|
|
1920
|
+
var lastPullAtByKey = /* @__PURE__ */ new Map();
|
|
1921
|
+
function shouldRunBranch(sessionId, workdir) {
|
|
1922
|
+
return lastBranchKey !== sessionWorkdirKey(sessionId, workdir);
|
|
1922
1923
|
}
|
|
1923
|
-
function
|
|
1924
|
-
|
|
1924
|
+
function markBranchDone(sessionId, workdir) {
|
|
1925
|
+
lastBranchKey = sessionWorkdirKey(sessionId, workdir);
|
|
1926
|
+
}
|
|
1927
|
+
function shouldRunPull(sessionId, workdir) {
|
|
1928
|
+
const key = sessionWorkdirKey(sessionId, workdir);
|
|
1929
|
+
const last = lastPullAtByKey.get(key);
|
|
1925
1930
|
if (last == null) {
|
|
1926
1931
|
return true;
|
|
1927
1932
|
}
|
|
1928
1933
|
return Date.now() - last >= PULL_TTL_MS;
|
|
1929
1934
|
}
|
|
1930
|
-
function markPullDone(sessionId) {
|
|
1931
|
-
|
|
1935
|
+
function markPullDone(sessionId, workdir) {
|
|
1936
|
+
lastPullAtByKey.set(sessionWorkdirKey(sessionId, workdir), Date.now());
|
|
1932
1937
|
}
|
|
1933
1938
|
|
|
1934
1939
|
// src/commands/connect/run-slot-pool.ts
|
|
@@ -1941,10 +1946,10 @@ function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT) {
|
|
|
1941
1946
|
active += 1;
|
|
1942
1947
|
return Promise.resolve();
|
|
1943
1948
|
}
|
|
1944
|
-
return new Promise((
|
|
1949
|
+
return new Promise((resolve5) => {
|
|
1945
1950
|
waiters.push(() => {
|
|
1946
1951
|
active += 1;
|
|
1947
|
-
|
|
1952
|
+
resolve5();
|
|
1948
1953
|
});
|
|
1949
1954
|
});
|
|
1950
1955
|
};
|
|
@@ -1978,6 +1983,8 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
1978
1983
|
if (isUserCancelled(ctx)) return;
|
|
1979
1984
|
if (signal.aborted) return;
|
|
1980
1985
|
const messageId = msg.messageId;
|
|
1986
|
+
const workdir = requireRemoteWorkdir(msg.workdir);
|
|
1987
|
+
const apmRoot = workspaceApmDir(workdir);
|
|
1981
1988
|
const runStep = async (step, fn) => {
|
|
1982
1989
|
const startedAt = Date.now();
|
|
1983
1990
|
try {
|
|
@@ -1995,24 +2002,18 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
1995
2002
|
"status-typing",
|
|
1996
2003
|
() => updateMessageStatus(cfg, messageId, "TYPING")
|
|
1997
2004
|
);
|
|
1998
|
-
if (shouldRunBranch(msg.sessionId)) {
|
|
2005
|
+
if (shouldRunBranch(msg.sessionId, workdir)) {
|
|
1999
2006
|
if (signal.aborted) return;
|
|
2000
|
-
await runStep(
|
|
2001
|
-
|
|
2002
|
-
() => runBranch(msg.sessionId, { cwd: msg.workdir })
|
|
2003
|
-
);
|
|
2004
|
-
markBranchDone(msg.sessionId);
|
|
2007
|
+
await runStep("branch", () => runBranch(msg.sessionId, { cwd: workdir }));
|
|
2008
|
+
markBranchDone(msg.sessionId, workdir);
|
|
2005
2009
|
} else {
|
|
2006
2010
|
console.log(`[apm] step=branch skipped sessionId=${msg.sessionId}`);
|
|
2007
2011
|
}
|
|
2008
2012
|
let pullRan = false;
|
|
2009
|
-
if (shouldRunPull(msg.sessionId)) {
|
|
2013
|
+
if (shouldRunPull(msg.sessionId, workdir)) {
|
|
2010
2014
|
if (signal.aborted) return;
|
|
2011
|
-
await runStep(
|
|
2012
|
-
|
|
2013
|
-
() => runPull(msg.sessionId, workspaceApmDir(msg.workdir))
|
|
2014
|
-
);
|
|
2015
|
-
markPullDone(msg.sessionId);
|
|
2015
|
+
await runStep("pull", () => runPull(msg.sessionId, workdir));
|
|
2016
|
+
markPullDone(msg.sessionId, workdir);
|
|
2016
2017
|
pullRan = true;
|
|
2017
2018
|
} else {
|
|
2018
2019
|
console.log(`[apm] step=pull skipped sessionId=${msg.sessionId}`);
|
|
@@ -2021,7 +2022,7 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
2021
2022
|
if (signal.aborted) return;
|
|
2022
2023
|
await runStep(
|
|
2023
2024
|
"commit-pull",
|
|
2024
|
-
() => commitWorkingTreeIfDirty(
|
|
2025
|
+
() => commitWorkingTreeIfDirty(workdir, "fix: apm pull")
|
|
2025
2026
|
);
|
|
2026
2027
|
} else {
|
|
2027
2028
|
console.log(`[apm] step=commit-pull skipped sessionId=${msg.sessionId}`);
|
|
@@ -2037,7 +2038,7 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
2037
2038
|
prompt: msg.content,
|
|
2038
2039
|
model: msg.model,
|
|
2039
2040
|
apiKey: msg.apiKey,
|
|
2040
|
-
workdir
|
|
2041
|
+
workdir,
|
|
2041
2042
|
user: msg.user
|
|
2042
2043
|
},
|
|
2043
2044
|
{ signal }
|
|
@@ -2045,11 +2046,11 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
|
|
|
2045
2046
|
);
|
|
2046
2047
|
await runStep(
|
|
2047
2048
|
"sync-documents",
|
|
2048
|
-
() => syncSessionDocuments(cfg, msg.sessionId,
|
|
2049
|
+
() => syncSessionDocuments(cfg, msg.sessionId, apmRoot)
|
|
2049
2050
|
);
|
|
2050
2051
|
await runStep(
|
|
2051
2052
|
"commit-files",
|
|
2052
|
-
() => commitWorkingTreeIfDirty(
|
|
2053
|
+
() => commitWorkingTreeIfDirty(workdir, "chore(apm): commit working tree")
|
|
2053
2054
|
);
|
|
2054
2055
|
await runStep(
|
|
2055
2056
|
"status-success",
|
|
@@ -2109,7 +2110,7 @@ async function runConnect(options) {
|
|
|
2109
2110
|
}
|
|
2110
2111
|
const url = buildAgentWsUrl(cfg.baseUrl, resolveApiKey(cfg));
|
|
2111
2112
|
console.log(`[apm] \u8FDE\u63A5 ${cfg.baseUrl} \u2026`);
|
|
2112
|
-
await new Promise((
|
|
2113
|
+
await new Promise((resolve5, reject) => {
|
|
2113
2114
|
const ws = new WebSocket(url);
|
|
2114
2115
|
let stopHeartbeat;
|
|
2115
2116
|
let shuttingDown = false;
|
|
@@ -2138,7 +2139,7 @@ async function runConnect(options) {
|
|
|
2138
2139
|
]);
|
|
2139
2140
|
} catch {
|
|
2140
2141
|
}
|
|
2141
|
-
|
|
2142
|
+
resolve5();
|
|
2142
2143
|
process.exit(code);
|
|
2143
2144
|
};
|
|
2144
2145
|
ws.on("open", () => {
|
|
@@ -2226,11 +2227,11 @@ import path5 from "node:path";
|
|
|
2226
2227
|
|
|
2227
2228
|
// src/commands/deploy/internal/apm-config.ts
|
|
2228
2229
|
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
|
|
2229
|
-
import { resolve as
|
|
2230
|
+
import { resolve as resolve4 } from "node:path";
|
|
2230
2231
|
function loadApmConfig(options) {
|
|
2231
|
-
const p =
|
|
2232
|
+
const p = resolve4(
|
|
2232
2233
|
process.cwd(),
|
|
2233
|
-
options?.configPath ??
|
|
2234
|
+
options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
|
|
2234
2235
|
);
|
|
2235
2236
|
if (!existsSync9(p)) {
|
|
2236
2237
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
@@ -2497,17 +2498,17 @@ var DockerodeClient = class {
|
|
|
2497
2498
|
await this.client.getImage(image).remove({ force: true });
|
|
2498
2499
|
}
|
|
2499
2500
|
async pullImage(image, auth) {
|
|
2500
|
-
const stream = await new Promise((
|
|
2501
|
+
const stream = await new Promise((resolve5, reject) => {
|
|
2501
2502
|
const pullOptions = auth ? { authconfig: auth } : void 0;
|
|
2502
2503
|
this.client.pull(image, pullOptions, (err, output) => {
|
|
2503
2504
|
if (err || !output) {
|
|
2504
2505
|
reject(err ?? new Error("docker pull \u8FD4\u56DE\u7A7A\u8F93\u51FA"));
|
|
2505
2506
|
return;
|
|
2506
2507
|
}
|
|
2507
|
-
|
|
2508
|
+
resolve5(output);
|
|
2508
2509
|
});
|
|
2509
2510
|
});
|
|
2510
|
-
await new Promise((
|
|
2511
|
+
await new Promise((resolve5, reject) => {
|
|
2511
2512
|
this.client.modem.followProgress(
|
|
2512
2513
|
stream,
|
|
2513
2514
|
(err) => {
|
|
@@ -2515,7 +2516,7 @@ var DockerodeClient = class {
|
|
|
2515
2516
|
reject(err);
|
|
2516
2517
|
return;
|
|
2517
2518
|
}
|
|
2518
|
-
|
|
2519
|
+
resolve5();
|
|
2519
2520
|
},
|
|
2520
2521
|
() => void 0
|
|
2521
2522
|
);
|
|
@@ -3006,14 +3007,14 @@ var MinioClient = class {
|
|
|
3006
3007
|
async deleteObjectsByPrefix(bucket, prefix) {
|
|
3007
3008
|
const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
|
|
3008
3009
|
const keys = [];
|
|
3009
|
-
await new Promise((
|
|
3010
|
+
await new Promise((resolve5, reject) => {
|
|
3010
3011
|
objectsStream.on("data", (obj) => {
|
|
3011
3012
|
if (obj.name) {
|
|
3012
3013
|
keys.push(obj.name);
|
|
3013
3014
|
}
|
|
3014
3015
|
});
|
|
3015
3016
|
objectsStream.on("error", reject);
|
|
3016
|
-
objectsStream.on("end",
|
|
3017
|
+
objectsStream.on("end", resolve5);
|
|
3017
3018
|
});
|
|
3018
3019
|
const chunkSize = 500;
|
|
3019
3020
|
for (let i = 0; i < keys.length; i += chunkSize) {
|
|
@@ -3251,7 +3252,7 @@ async function ensureRemoteDir(sftp, dir) {
|
|
|
3251
3252
|
}
|
|
3252
3253
|
}
|
|
3253
3254
|
function execCommand(client, command) {
|
|
3254
|
-
return new Promise((
|
|
3255
|
+
return new Promise((resolve5, reject) => {
|
|
3255
3256
|
client.exec(command, (err, stream) => {
|
|
3256
3257
|
if (err) return reject(err);
|
|
3257
3258
|
let stdout = "";
|
|
@@ -3261,7 +3262,7 @@ function execCommand(client, command) {
|
|
|
3261
3262
|
reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
|
|
3262
3263
|
return;
|
|
3263
3264
|
}
|
|
3264
|
-
|
|
3265
|
+
resolve5(stdout);
|
|
3265
3266
|
}).on("data", (data) => {
|
|
3266
3267
|
stdout += data.toString();
|
|
3267
3268
|
});
|
package/package.json
CHANGED
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
1. 用 **Read** 工具阅读本端计划:前端读 `.apm/sessions/<会话ID>/docs/FRONTEND-PLAN.md`,后端读 `docs/BACKEND-PLAN.md`;计划不存在则退出流程并回复说明(兼容旧流程:若存在 `PRD.md` + `FRONTEND.md` / `BACKEND.md` + `API.md`,按旧文档执行)。
|
|
8
8
|
2. 前端涉及接口对接时,以后端计划中的「API 契约」章节为准,**不等后端部署完成**;契约没写清的字段 `@后端` 确认,禁止自行猜测。
|
|
9
|
-
3.
|
|
9
|
+
3. **假设门禁(开发前必须检查)**:查看计划「依据与假设」章节——
|
|
10
|
+
- 「假设」仍有未确认项:**Read** `.apm/sessions/<会话ID>/messages.xml`,查找项目经理是否已回复确认;
|
|
11
|
+
- 项目经理已回复:先按 `.apm/skills/apm-write-plan/SKILL.md` 步骤 5 把确认结果**回填进计划文档并同步**(确认的假设移入「依据」,否定的修订实现步骤与白名单),然后再开发;
|
|
12
|
+
- 项目经理未回复:`@项目经理` 列出待确认假设,**停止本次开发**。
|
|
13
|
+
严禁带着未回填的澄清直接开发——项目经理在聊天里给过的口径若没落进计划,开发与 diff 评审都不会认。
|
|
10
14
|
|
|
11
15
|
### 步骤 2: 明确开发模式
|
|
12
16
|
|
|
@@ -44,6 +44,20 @@
|
|
|
44
44
|
- **无假设**:回复计划已就绪,可进入测试要点编写。
|
|
45
45
|
- 前端计划依赖的接口后端契约还没出:在计划「期望接口」小节写出前端期望的接口形态,回复时 `@后端` 对齐,不要空等。
|
|
46
46
|
|
|
47
|
+
### 步骤 5:假设回填(项目经理确认后必须执行)
|
|
48
|
+
|
|
49
|
+
当你被安排「根据项目经理的确认更新计划」,或发现项目经理已回复假设确认时:
|
|
50
|
+
|
|
51
|
+
1. **Read** `.apm/sessions/<会话ID>/messages.xml`,找到项目经理针对假设的最新回复,**逐条对照你列出的假设**,不要只看 speakContext 的转述。
|
|
52
|
+
2. 更新计划文档:
|
|
53
|
+
- 被确认的假设 → **移入「依据」表格**,来源写「项目经理确认(第 N 轮)」;
|
|
54
|
+
- 被否定或修正的假设 → 按项目经理给出的口径**修订「实现步骤」与「改动文件白名单」**;
|
|
55
|
+
- 项目经理没有回应的假设 → 保留在「假设」中,再次 `@项目经理` 追问。
|
|
56
|
+
3. 重新执行 `apm sync-document` 同步计划。
|
|
57
|
+
4. 回复消息:逐条说明每个假设的处理结果(确认采纳 / 按口径修订了什么),全部解决则声明「假设已清零,可进入测试要点编写」。
|
|
58
|
+
|
|
59
|
+
**禁止**跳过回填直接开发:澄清结论必须落进计划文档,后续开发与 diff 评审都只认计划文档,不认聊天记录。
|
|
60
|
+
|
|
47
61
|
---
|
|
48
62
|
|
|
49
63
|
## 写作要求
|