fix: support rolling commander brief profile

This commit is contained in:
Codex
2026-05-21 07:52:26 +00:00
parent 9911a9e736
commit b5486a61ab
4 changed files with 202 additions and 26 deletions
+109 -1
View File
@@ -127,6 +127,52 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
].join("\n"),
html_url: "https://github.com/pikasTech/unidesk/issues/60",
};
const legacyCommanderBriefIssue = {
...issue,
id: 2024,
number: 24,
title: "指挥简报",
body: [
"# 指挥简报",
"",
"## 常驻观察与长期建议",
"",
"- 维持 Code Queue 指挥态势。",
"",
"## 更新 2026-05-20 17:28 北京时间",
"",
"- 已完成历史简报入口维护。",
"",
].join("\n"),
html_url: "https://github.com/pikasTech/unidesk/issues/24",
};
const dailyCommanderBriefIssue = {
...issue,
id: 2046,
number: 46,
title: "2026-05-21 指挥简报(北京时间)",
body: [
"# 2026-05-21 指挥简报(北京时间)",
"",
"## 常驻观察与长期建议",
"",
"- 今日滚动简报使用每日 issue 主体维护。",
"",
"## 更新 2026-05-21 09:00 北京时间",
"",
"- 启动当日队列监督。",
"",
].join("\n"),
html_url: "https://github.com/pikasTech/unidesk/issues/46",
};
const nonBriefIssue = {
...issue,
id: 2047,
number: 47,
title: "普通任务 issue",
body: "# 普通任务\n\n## 背景\n\n- 不是指挥简报。\n",
html_url: "https://github.com/pikasTech/unidesk/issues/47",
};
const issueList = [
{
id: 2001,
@@ -351,6 +397,18 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
sendJson(res, 200, boardIssue);
return;
}
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues/24") {
sendJson(res, 200, legacyCommanderBriefIssue);
return;
}
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues/46") {
sendJson(res, 200, dailyCommanderBriefIssue);
return;
}
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues/47") {
sendJson(res, 200, nonBriefIssue);
return;
}
if (req.method === "GET" && req.url === "/repos/pikasTech/unidesk/issues/60") {
sendJson(res, 200, legacyBoardIssue);
return;
@@ -474,7 +532,7 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
const mock = await startMockGitHub();
const tmp = mkdtempSync(join(tmpdir(), "unidesk-gh-issue-guard-"));
const env = {
GH_TOKEN: "contract-token",
GH_TOKEN: "contract-token-should-not-print",
UNIDESK_GITHUB_API_URL: mock.baseUrl,
};
try {
@@ -707,9 +765,56 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
const briefWrongData = failedDataOf(briefWrongProfile.json ?? {});
const briefWrongGuard = briefWrongData.guard as JsonRecord;
assertCondition(Array.isArray(briefWrongGuard.failures) && briefWrongGuard.failures.includes("profile-issue-mismatch"), "explicit profile should check issue number", briefWrongGuard);
assertCondition(!briefWrongProfile.stdout.includes(env.GH_TOKEN) && !briefWrongProfile.stderr.includes(env.GH_TOKEN), "failed profile output must not print GH_TOKEN", {
stdout: briefWrongProfile.stdout,
stderr: briefWrongProfile.stderr,
});
const safeFile = join(tmp, "safe.md");
writeFileSync(safeFile, "# Code Queue\n\n## 看板(OPEN\n\n- multiline Markdown keeps `code` intact.\n- real newline follows.\n\n| a | b |\n| --- | --- |\n| 1 | 2 |\n", "utf8");
const validBriefFile = join(tmp, "valid-commander-brief.md");
writeFileSync(validBriefFile, [
"# 2026-05-21 指挥简报(北京时间)",
"",
"## 常驻观察与长期建议",
"",
"- 保持滚动简报正文只通过 body-file 更新。",
"",
"## 更新 2026-05-21 15:18 北京时间",
"",
"- 今日新增进展包含 `code` 和表格。",
"",
"| 项 | 状态 |",
"| --- | --- |",
"| CLI | guarded |",
"",
].join("\n"), "utf8");
const legacyBriefDryRun = await runCli(["gh", "issue", "update", "24", "--repo", "pikasTech/unidesk", "--mode", "replace", "--body-file", validBriefFile, "--body-profile", "commander-brief", "--dry-run"], env);
assertCondition(legacyBriefDryRun.status === 0, "#24 explicit commander-brief profile should remain compatible", legacyBriefDryRun.json ?? { stdout: legacyBriefDryRun.stdout });
const legacyBriefData = dataOf(legacyBriefDryRun.json ?? {});
const legacyBriefGuard = legacyBriefData.guard as JsonRecord;
const legacyBriefProfile = legacyBriefGuard.profile as JsonRecord;
assertCondition(legacyBriefGuard.ok === true && legacyBriefProfile.issueMatchesProfile === true && legacyBriefProfile.issueMatchReason === "legacy-issue-number", "#24 commander-brief profile should pass by legacy issue number", legacyBriefGuard);
const dailyBriefDryRun = await runCli(["gh", "issue", "update", "46", "--repo", "pikasTech/unidesk", "--mode", "replace", "--body-file", validBriefFile, "--body-profile", "commander-brief", "--dry-run"], env);
assertCondition(dailyBriefDryRun.status === 0, "daily commander brief issue should pass commander-brief dry-run guard", dailyBriefDryRun.json ?? { stdout: dailyBriefDryRun.stdout });
const dailyBriefData = dataOf(dailyBriefDryRun.json ?? {});
const dailyBriefGuard = dailyBriefData.guard as JsonRecord;
const dailyBriefProfile = dailyBriefGuard.profile as JsonRecord;
assertCondition(dailyBriefGuard.ok === true && dailyBriefProfile.issueMatchesProfile === true && dailyBriefProfile.issueMatchReason === "daily-title", "daily commander brief profile should match by title", dailyBriefGuard);
assertCondition(dailyBriefData.containsLiteralBackslashN === false && dailyBriefData.containsMarkdownTable === true, "daily brief dry-run should preserve markdown safety signals", dailyBriefData);
const nonBriefDryRun = await runCli(["gh", "issue", "update", "47", "--repo", "pikasTech/unidesk", "--mode", "replace", "--body-file", validBriefFile, "--body-profile", "commander-brief", "--dry-run"], env);
assertCondition(nonBriefDryRun.status !== 0, "non-brief issue should fail commander-brief profile", nonBriefDryRun.json ?? { stdout: nonBriefDryRun.stdout });
const nonBriefData = failedDataOf(nonBriefDryRun.json ?? {});
const nonBriefGuard = nonBriefData.guard as JsonRecord;
assertCondition(Array.isArray(nonBriefGuard.failures) && nonBriefGuard.failures.includes("profile-issue-mismatch"), "non-brief issue should report profile-issue-mismatch", nonBriefGuard);
assertCondition(!nonBriefDryRun.stdout.includes(env.GH_TOKEN) && !nonBriefDryRun.stderr.includes(env.GH_TOKEN), "non-brief failure must not print GH_TOKEN", {
stdout: nonBriefDryRun.stdout,
stderr: nonBriefDryRun.stderr,
});
const requestCountBeforeDryRun = mock.requests.length;
const safeDryRun = await runCli(["gh", "issue", "edit", "20", "--repo", "pikasTech/unidesk", "--body-file", safeFile, "--dry-run"], env);
assertCondition(safeDryRun.status === 0, "safe issue edit dry-run should succeed", safeDryRun.json ?? { stdout: safeDryRun.stdout });
@@ -825,6 +930,9 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
"unsupported --json fields fail structurally",
"issue edit --body-file rejects literal null",
"#20/#24 body profile guards reject missing headings or wrong profile",
"#24 commander-brief profile remains compatible",
"daily commander brief issues match commander-brief profile by title",
"non-brief issues fail commander-brief profile without printing token",
"dry-run reports old/new body safety and does not PATCH",
"multiline Markdown and backticks are not polluted",
"expect-updated-at stale write protection blocks PATCH",