fix: guard board against commander brief pollution

This commit is contained in:
Codex
2026-05-21 13:33:49 +00:00
parent 5cb5cc1b43
commit 133d417d01
3 changed files with 72 additions and 1 deletions
+44 -1
View File
@@ -41,6 +41,11 @@ const ISSUE_BODY_PROFILES = {
const DAILY_COMMANDER_BRIEF_TITLE_PATTERN = /^\d{4}-\d{2}-\d{2}\s+$/u;
const DAILY_COMMANDER_BRIEF_BODY_HEADING_PATTERN = /^#\s+\d{4}-\d{2}-\d{2}\s+\s*$/u;
const LEGACY_COMMANDER_BRIEF_BODY_HEADING_PATTERN = /^#\s+\s*$/u;
const COMMANDER_BRIEF_UPDATE_HEADING_PATTERNS = [
/^##\s+\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+\s*$/u,
/^##\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+\s*$/u,
/^###\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+CST[:].*$/u,
] as const;
type IssueViewJsonField = typeof ISSUE_VIEW_JSON_FIELDS[number];
type IssueListJsonField = typeof ISSUE_LIST_JSON_FIELDS[number];
@@ -780,6 +785,31 @@ function isTimelineHeading(line: string): boolean {
return /^##\s+\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}\s+\s*$/u.test(line.trimEnd());
}
function isCommanderBriefUpdateHeading(line: string): boolean {
const trimmed = line.trimEnd();
return COMMANDER_BRIEF_UPDATE_HEADING_PATTERNS.some((pattern) => pattern.test(trimmed));
}
function commanderBriefUpdateHeadings(body: string): string[] {
return normalizeNewlines(body)
.split("\n")
.map((line) => line.trimEnd())
.filter((line) => isCommanderBriefUpdateHeading(line));
}
function codeQueueBoardCommanderBriefHint(boardIssueNumber: number, body?: string): Record<string, unknown> | null {
if (boardIssueNumber !== CODE_QUEUE_BOARD_TARGET_ISSUE) return null;
const headings = body === undefined ? [] : commanderBriefUpdateHeadings(body);
return {
warning: "#20 is the long-term board only; do not write daily commander brief update sections into #20.",
route: "Move daily progress notes to the daily rolling commander brief issue referenced in #20's 指挥简报索引, using --body-profile commander-brief.",
forbiddenHeadings: ["## 更新 YYYY-MM-DD HH:mm 北京时间", "## YYYY-MM-DD HH:mm 北京时间指挥更新", "### YYYY-MM-DD HH:mm CST..."],
suggestedCommand: "bun scripts/cli.ts gh issue update <daily-brief-issue> --mode replace --body-file <file> --body-profile commander-brief --expect-body-sha <sha>",
detected: headings.length > 0,
detectedHeadings: headings,
};
}
function extractTimelineSections(markdown: string): CommanderBriefSection[] {
const normalized = normalizeNewlines(markdown);
const lines = normalized.split("\n");
@@ -2795,6 +2825,7 @@ async function issueBoardRowMutation(
bodySha: bodySha(boardIssue.body ?? ""),
updatedAt: boardIssue.updated_at ?? null,
},
codeQueueBoardHint: codeQueueBoardCommanderBriefHint(options.boardIssue, planned.newBody),
issueNumber,
operation: typeof planned.plan.operation === "string" ? planned.plan.operation : mutationKey,
dryRun: effectiveDryRun,
@@ -2864,6 +2895,7 @@ async function issueBoardRowList(repo: string, token: string, options: GitHubOpt
repo,
dryRun: true,
readOnly: true,
codeQueueBoardHint: codeQueueBoardCommanderBriefHint(options.boardIssue, boardIssue.body ?? ""),
boardIssue: {
number: boardIssue.number,
title: boardIssue.title,
@@ -2895,6 +2927,7 @@ async function issueBoardRowGet(repo: string, token: string, issueNumber: number
repo,
dryRun: true,
readOnly: true,
codeQueueBoardHint: codeQueueBoardCommanderBriefHint(options.boardIssue, boardIssue.body ?? ""),
boardIssue: {
number: boardIssue.number,
title: boardIssue.title,
@@ -2948,6 +2981,7 @@ async function issueBoardRowUpdate(repo: string, token: string, issueNumber: num
bodySha: bodySha(boardIssue.body ?? ""),
updatedAt: boardIssue.updated_at ?? null,
},
codeQueueBoardHint: codeQueueBoardCommanderBriefHint(options.boardIssue, planned.newBody),
issueNumber,
dryRun: effectiveDryRun,
planned: true,
@@ -3170,11 +3204,14 @@ function validateIssueBodyGuard(repo: string, issueNumber: number, body: string,
const isBlank = trimmed.length === 0;
const isShort = trimmed.length > 0 && trimmed.length < MIN_SAFE_ISSUE_BODY_CHARS;
const profileValidation = issueProfileValidation(issueNumber, body, options.bodyProfile, profileContext);
const boardBriefHint = codeQueueBoardCommanderBriefHint(issueNumber, body);
const boardContainsCommanderBriefUpdates = boardBriefHint?.detected === true;
const profileOk = validationBoolean(profileValidation, "ok");
const failures: string[] = [];
if (isLiteralNull) failures.push("literal-null-body");
if (isBlank) failures.push("blank-body");
if (isShort && !options.allowShortBody) failures.push("short-body");
if (boardContainsCommanderBriefUpdates) failures.push("code-queue-board-contains-commander-brief-updates");
if (!profileOk) {
if (profileValidation.issueMatchesProfile === false) failures.push("profile-issue-mismatch");
const missingHeadings = validationStringArray(profileValidation, "missingHeadings");
@@ -3196,6 +3233,7 @@ function validateIssueBodyGuard(repo: string, issueNumber: number, body: string,
bodySource: { kind: "body-file", path: options.bodyFile ?? null },
bodyPreview: preview(body),
bodyPreviewLines: previewLines(body),
codeQueueBoardHint: boardBriefHint,
...bodySafetySignals(body),
isLiteralNull,
isBlank,
@@ -3208,12 +3246,14 @@ function validateIssueBodyGuard(repo: string, issueNumber: number, body: string,
function issueEditGuardSummary(issueNumber: number, body: string, options: GitHubOptions, profileContext: IssueProfileValidationContext = {}): Record<string, unknown> {
const trimmed = body.trim();
const profile = issueProfileValidation(issueNumber, body, options.bodyProfile, profileContext);
const boardBriefHint = codeQueueBoardCommanderBriefHint(issueNumber, body);
const warnings: string[] = [];
if (trimmed.length > 0 && trimmed.length < MIN_SAFE_ISSUE_BODY_CHARS) {
warnings.push(options.allowShortBody ? "short-body-allowed" : "short-body-would-fail");
}
if (options.allowShortBody) warnings.push("allow-short-body enabled; caller accepted short-body corruption risk");
if (profile.ok === false) warnings.push("profile guard would fail");
if (boardBriefHint?.detected === true) warnings.push("code-queue-board-contains-commander-brief-updates");
const signals = bodySafetySignals(body);
const shellPollution = signals.shellPollution as Record<string, unknown>;
if (shellPollution.suspected === true) warnings.push("shell-pollution-suspected");
@@ -3221,11 +3261,13 @@ function issueEditGuardSummary(issueNumber: number, body: string, options: GitHu
ok: trimmed.length > 0
&& trimmed.toLowerCase() !== "null"
&& (trimmed.length >= MIN_SAFE_ISSUE_BODY_CHARS || options.allowShortBody)
&& profile.ok === true,
&& profile.ok === true
&& boardBriefHint?.detected !== true,
minSafeBodyChars: MIN_SAFE_ISSUE_BODY_CHARS,
allowShortBody: options.allowShortBody,
warnings,
profile,
codeQueueBoardHint: boardBriefHint,
...signals,
};
}
@@ -3647,6 +3689,7 @@ async function issueRead(repo: string, token: string, issueNumber: number, jsonF
command: commandName,
repo,
issue: issueSummary(issue),
codeQueueBoardHint: codeQueueBoardCommanderBriefHint(issueNumber, issue.body ?? ""),
...(comments === null ? {} : { comments: comments.map(commentSummary) }),
...(jsonFields === undefined ? {} : {
jsonFields,