fix: reduce board audit noise

This commit is contained in:
Codex
2026-05-20 22:08:23 +00:00
parent 27b14bef76
commit acc03b10f4
4 changed files with 91 additions and 6 deletions
+33 -1
View File
@@ -97,13 +97,15 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
"| --- | --- | --- | --- | --- | --- |",
"| #20 | master | meta | governance | 关注:#19 / #26 / #30 | active |",
"| #35 | master | pass | cq-35 | 当前关注点:#19 / #26 / #30 | doing |",
"| #40 | | | cq-40 | 复核:#19 / #26 / #30 | |",
"| [#45](https://github.com/pikasTech/unidesk/issues/45) #20 总看板缺少自动覆盖审计 | master | pass | cq-45 | 复核:#20 / #24 | doing |",
"| #40 | — | — | — | 复核:#19 / #26 / #30 | — |",
"",
"## 看板(CLOSED",
"",
"| Issue | Branch | 验收状态 | 相关 Code Queue 任务 | 当前关注点 | 进度 |",
"| --- | --- | --- | --- | --- | --- |",
"| #24 | master | meta | brief | 常驻:#24 / #20 | active |",
"| [#18](https://github.com/pikasTech/unidesk/issues/18) 基于 #4 评审结论修复 | master | pass | — | 历史治理归档 | done |",
"| #36 | master | pending | cq-36 | 复核:#35 / #40 | done |",
"",
].join("\n"),
@@ -237,6 +239,19 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
created_at: "2026-05-20T01:00:00Z",
updated_at: "2026-05-20T02:00:00Z",
},
{
id: 2045,
number: 45,
title: "#20 总看板缺少自动覆盖审计",
body: "audit body",
state: "open",
html_url: "https://github.com/pikasTech/unidesk/issues/45",
comments: 0,
user: { login: "tester" },
labels: [],
created_at: "2026-05-20T01:30:00Z",
updated_at: "2026-05-20T02:30:00Z",
},
];
const legacyBoardOpenIssues = [
{
@@ -256,6 +271,19 @@ async function startMockGitHub(): Promise<{ baseUrl: string; requests: MockReque
issueList[1],
];
const boardClosedIssues = [
{
id: 2018,
number: 18,
title: "基于 #4 评审结论修复",
body: "closed body",
state: "closed",
html_url: "https://github.com/pikasTech/unidesk/issues/18",
comments: 0,
user: { login: "runner" },
labels: [],
created_at: "2026-05-18T02:00:00Z",
updated_at: "2026-05-20T03:00:00Z",
},
{
id: 2040,
number: 40,
@@ -497,15 +525,19 @@ export async function runGhCliIssueGuardContract(): Promise<JsonRecord> {
assertCondition(boardAuditData.command === "issue board-audit" && boardAuditData.dryRun === true && boardAuditData.readOnly === true, "board-audit should be explicit read-only dry-run", boardAuditData);
const missingOpenIssues = boardAuditData.missingOpenIssues as JsonRecord[];
assertCondition(Array.isArray(missingOpenIssues) && missingOpenIssues.some((issue) => issue.number === 36), "board-audit should report open issue missing from OPEN table", missingOpenIssues);
assertCondition(!missingOpenIssues.some((issue) => issue.number === 45), "board-audit should use the primary markdown issue link as the Issue-column row key", missingOpenIssues);
const closedInOpenRows = boardAuditData.closedInOpenRows as JsonRecord[];
assertCondition(Array.isArray(closedInOpenRows) && closedInOpenRows.some((issue) => issue.number === 40), "board-audit should report closed issue still in OPEN table", closedInOpenRows);
const missingClosedRows = boardAuditData.missingClosedRows as JsonRecord[];
assertCondition(Array.isArray(missingClosedRows) && missingClosedRows.some((issue) => issue.number === 40) && missingClosedRows.some((issue) => issue.number === 41), "board-audit should report closed issues missing from CLOSED table", missingClosedRows);
assertCondition(!missingClosedRows.some((issue) => issue.number === 18), "board-audit should ignore explanatory issue references after the primary Issue-column markdown link", missingClosedRows);
const openInClosedRows = boardAuditData.openInClosedRows as JsonRecord[];
assertCondition(Array.isArray(openInClosedRows) && openInClosedRows.some((issue) => issue.number === 36), "board-audit should report open issue placed in CLOSED table", openInClosedRows);
const rowValidationWarnings = boardAuditData.rowValidationWarnings as JsonRecord[];
assertCondition(Array.isArray(rowValidationWarnings) && rowValidationWarnings.some((warning) => warning.issueNumber === 40 && Array.isArray(warning.missingColumns) && (warning.missingColumns as unknown[]).includes("branch") && (warning.missingColumns as unknown[]).includes("acceptance") && (warning.missingColumns as unknown[]).includes("progress")), "board-audit should report missing required board columns", rowValidationWarnings);
assertCondition(!rowValidationWarnings.some((warning) => warning.kind === "multiple-issue-references"), "board-audit should not treat explanatory issue references outside the Issue column as row-key conflicts", rowValidationWarnings);
assertCondition(!rowValidationWarnings.some((warning) => warning.kind === "multiple-issue-references" && String(warning.rowPreview ?? "").includes("#20 总看板")), "board-audit should not warn when an Issue-column title mentions #20 after primary #45 link", rowValidationWarnings);
assertCondition(!rowValidationWarnings.some((warning) => warning.kind === "multiple-issue-references" && String(warning.rowPreview ?? "").includes("基于 #4")), "board-audit should not warn when an Issue-column title mentions #4 after primary #18 link", rowValidationWarnings);
assertCondition(!rowValidationWarnings.some((warning) => Array.isArray(warning.missingColumns) && (warning.missingColumns as unknown[]).includes("relatedTask")), "board-audit should recognize relatedTask from the current header", rowValidationWarnings);
const ignoredIssues = boardAuditData.ignoredIssues as JsonRecord[];
assertCondition(Array.isArray(ignoredIssues) && ignoredIssues.some((issue) => issue.number === 20 && issue.reason === "known-meta") && ignoredIssues.some((issue) => issue.number === 24 && issue.reason === "known-meta"), "board-audit should ignore configured known meta issues", ignoredIssues);
+56 -3
View File
@@ -1279,10 +1279,60 @@ function extractIssueNumbers(text: string): number[] {
return numbers;
}
function cellHasMeaningfulValue(value: string | undefined): boolean {
function primaryBoardIssueNumberFromIssueCell(issueCell: string | undefined): number | null {
if (issueCell === undefined) return null;
const markdownLinkPattern = /\[[^\]]*]\(([^)]*)\)/g;
let match = markdownLinkPattern.exec(issueCell);
while (match !== null) {
const issueNumber = /\/issues\/(\d+)/u.exec(match[1] ?? "")?.[1];
if (issueNumber !== undefined) {
const value = Number(issueNumber);
if (Number.isInteger(value) && value > 0) return value;
}
match = markdownLinkPattern.exec(issueCell);
}
const leadingIssueReference = /^\s*#(\d+)\b/u.exec(issueCell)?.[1];
if (leadingIssueReference !== undefined) {
const value = Number(leadingIssueReference);
if (Number.isInteger(value) && value > 0) return value;
}
return null;
}
function normalizedBoardCellPlaceholder(value: string): { spaced: string; compact: string } {
const spaced = stripMarkdownInline(value).toLowerCase().replace(/\s+/g, " ").trim();
return { spaced, compact: spaced.replace(/\s+/g, "") };
}
function isRelatedTaskNoTaskPlaceholder(value: string): boolean {
const normalized = normalizedBoardCellPlaceholder(value);
return [
"-",
"",
"",
"n/a",
"na",
"none",
"no task",
"no-task",
"no code queue task",
"not applicable",
].includes(normalized.spaced) || [
"",
"",
"",
"",
"codequeue任务",
"codequeuetask",
"",
].includes(normalized.compact);
}
function cellHasMeaningfulValue(value: string | undefined, column: BoardRequiredColumn): boolean {
if (value === undefined) return false;
const stripped = stripMarkdownInline(value);
if (stripped.length === 0) return false;
if (column === "relatedTask" && isRelatedTaskNoTaskPlaceholder(value)) return true;
return !["-", "", "n/a", "na", "todo", "tbd", "", "", ""].includes(stripped.toLowerCase());
}
@@ -1318,7 +1368,10 @@ function parseBoardTables(body: string): { sections: BoardTableSection[]; warnin
if (!isMarkdownTableSeparator(lines[rowIndex])) {
const cells = markdownCells(lines[rowIndex]);
const issueCell = issueColumnIndex === null ? undefined : cells[issueColumnIndex];
const issueNumbers = issueColumnIndex === null ? extractIssueNumbers(lines[rowIndex]) : extractIssueNumbers(issueCell ?? "");
const primaryIssueNumber = issueColumnIndex === null ? null : primaryBoardIssueNumberFromIssueCell(issueCell);
const issueNumbers = issueColumnIndex === null
? extractIssueNumbers(lines[rowIndex])
: (primaryIssueNumber === null ? extractIssueNumbers(issueCell ?? "") : [primaryIssueNumber]);
const columns: Partial<Record<BoardRequiredColumn, string>> = {};
for (const column of BOARD_AUDIT_REQUIRED_COLUMNS) {
const cellIndex = columnMap.get(column);
@@ -1359,7 +1412,7 @@ function parseBoardTables(body: string): { sections: BoardTableSection[]; warnin
rowPreview: preview(row.raw),
});
}
const missingColumns = BOARD_AUDIT_REQUIRED_COLUMNS.filter((column) => !cellHasMeaningfulValue(columns[column]));
const missingColumns = BOARD_AUDIT_REQUIRED_COLUMNS.filter((column) => !cellHasMeaningfulValue(columns[column], column));
if (missingColumns.length > 0) {
warnings.push({
issueNumber: row.issueNumber,