fix: reduce board audit noise
This commit is contained in:
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user