fix: support rolling commander brief profile
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user