From c74cbf587ed2fa6e83a3e3978fafc58395b8536f Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Fri, 18 Sep 2026 15:51:37 +0200 Subject: [PATCH] fix: keep deprecated rule issues open on act-board Closing an act-board issue makes GitHub Projects v2 move its card to the Done option, which the ACT board renames "Approved, current". Deprecated rules were therefore shown as approved and current, regardless of the Deprecated status the Project sync writes. Every rule in the classifier snapshot now keeps an open issue, and issues closed by earlier runs are reopened. Deprecated still shows in the issue body and as the Project Status. Issues for rules missing from the snapshot are still closed, so those cards can still land in Done. Co-authored-by: Cursor --- .../__tests__/act-board.test.ts | 45 +++++++++++++++---- src/approval-report/act-board.ts | 15 +++---- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/approval-report/__tests__/act-board.test.ts b/src/approval-report/__tests__/act-board.test.ts index 4ca99db..70f8ce7 100644 --- a/src/approval-report/__tests__/act-board.test.ts +++ b/src/approval-report/__tests__/act-board.test.ts @@ -55,7 +55,7 @@ function boardIssue( number: Number.parseInt(rule.ruleId, 16) || 1, title: actBoardIssueTitle(rule), body: renderActBoardIssueBody(rule), - state: rule.status === "Deprecated" ? "closed" : "open", + state: "open", nodeId: `BOARD_${rule.ruleId}`, ...overrides, }; @@ -290,22 +290,19 @@ describe("upsertActBoardIssues", () => { expect(client.updateBoardIssue).not.toHaveBeenCalled(); }); - it("closes deprecated and snapshot-missing rule issues", async () => { + it("keeps deprecated rule issues open and closes snapshot-missing ones", async () => { const deprecated = row("674b10", { status: "Deprecated" }); - const oldDeprecated = boardIssue(deprecated, { - body: "old body", - state: "open", - }); + const oldDeprecated = boardIssue(deprecated, { body: "old body" }); const removed = boardIssue(row("2ee8b8")); const client = fakeClient({ issues: [oldDeprecated, removed] }); const result = await upsertActBoardIssues([deprecated], client); - expect(result.closed).toBe(2); + expect(result.closed).toBe(1); expect(client.updateBoardIssue).toHaveBeenCalledWith( expect.anything(), oldDeprecated.number, - expect.objectContaining({ state: "closed" }), + { body: renderActBoardIssueBody(deprecated) }, ); expect(client.updateBoardIssue).toHaveBeenCalledWith( expect.anything(), @@ -314,6 +311,38 @@ describe("upsertActBoardIssues", () => { ); }); + it("reopens a deprecated rule issue closed by an earlier run", async () => { + const deprecated = row("674b10", { status: "Deprecated" }); + const closedIssue = boardIssue(deprecated, { state: "closed" }); + const client = fakeClient({ issues: [closedIssue] }); + + const result = await upsertActBoardIssues([deprecated], client); + + expect(result.reopened).toBe(1); + expect(result.closed).toBe(0); + expect(client.updateBoardIssue).toHaveBeenCalledWith( + expect.anything(), + closedIssue.number, + { state: "open" }, + ); + }); + + it("creates a deprecated rule issue as open", async () => { + const deprecated = row("674b10", { status: "Deprecated" }); + const client = fakeClient(); + + const result = await upsertActBoardIssues([deprecated], client); + + expect(result.created).toBe(1); + expect(result.closed).toBe(0); + expect(client.createBoardIssue).toHaveBeenCalledWith( + expect.anything(), + actBoardIssueTitle(deprecated), + expect.stringContaining("**Status:** Deprecated"), + ); + expect(client.updateBoardIssue).not.toHaveBeenCalled(); + }); + it("adds new blockers and removes dropped sub-issues", async () => { const blocker = issue(12, "Current blocker", true); const currentRow = row("674b10", { diff --git a/src/approval-report/act-board.ts b/src/approval-report/act-board.ts index 3a2fafa..a1393c3 100644 --- a/src/approval-report/act-board.ts +++ b/src/approval-report/act-board.ts @@ -185,18 +185,11 @@ export async function upsertActBoardIssues( const ruleId = row.ruleId.toLowerCase(); const title = actBoardIssueTitle(row); const body = renderActBoardIssueBody(row); - const desiredState = row.status === "Deprecated" ? "closed" : "open"; let issue = managedIssues.get(ruleId); if (!issue) { issue = await client.createBoardIssue(boardRepository, title, body); result.created += 1; - if (desiredState === "closed") { - issue = await client.updateBoardIssue(boardRepository, issue.number, { - state: "closed", - }); - result.closed += 1; - } managedIssues.set(ruleId, issue); continue; } @@ -208,7 +201,10 @@ export async function upsertActBoardIssues( } = {}; if (issue.title !== title) update.title = title; if (normalizeBody(issue.body) !== normalizeBody(body)) update.body = body; - if (issue.state !== desiredState) update.state = desiredState; + // Every rule in the snapshot keeps an open issue, deprecated ones included. + // Projects v2 moves closed items to Done, which this Project renames + // "Approved, current"; the Deprecated status comes from the Project sync. + if (issue.state === "closed") update.state = "open"; if (Object.keys(update).length === 0) { unwrittenIssueNodeIds.add(issue.nodeId); @@ -219,8 +215,7 @@ export async function upsertActBoardIssues( update, ); managedIssues.set(ruleId, issue); - if (update.state === "closed") result.closed += 1; - else if (update.state === "open") result.reopened += 1; + if (update.state === "open") result.reopened += 1; else result.updated += 1; } }