diff --git a/actions/setup/js/expired_entity_search_helpers.cjs b/actions/setup/js/expired_entity_search_helpers.cjs index 0d7a7f8cb3..0fab75cb4d 100644 --- a/actions/setup/js/expired_entity_search_helpers.cjs +++ b/actions/setup/js/expired_entity_search_helpers.cjs @@ -97,9 +97,13 @@ async function searchEntitiesWithExpiration(github, owner, repo, config) { // Filter for entities with agentic workflow markers and expiration comments for (const entity of nodes) { - // Check if created by an agentic workflow (body contains "> AI generated by" at start of line) - const agenticPattern = /^> AI generated by/m; - const isAgenticWorkflow = entity.body && agenticPattern.test(entity.body); + // Check if created by an agentic workflow using XML comment markers + // Look for either: + // 1. (standalone workflow ID marker) + // 2. (full metadata marker) + const hasWorkflowId = entity.body && entity.body.includes("gh-aw-workflow-id:"); + const hasAgenticWorkflow = entity.body && entity.body.includes("gh-aw-agentic-workflow:"); + const isAgenticWorkflow = hasWorkflowId || hasAgenticWorkflow; if (isAgenticWorkflow) { agenticCount++; diff --git a/actions/setup/js/expired_entity_search_helpers.test.cjs b/actions/setup/js/expired_entity_search_helpers.test.cjs index c05fcea7fb..d3e3df5d23 100644 --- a/actions/setup/js/expired_entity_search_helpers.test.cjs +++ b/actions/setup/js/expired_entity_search_helpers.test.cjs @@ -26,7 +26,7 @@ describe("searchEntitiesWithExpiration", () => { number: 123, title: "Test Issue", url: "https://github.com/test-owner/test-repo/issues/123", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -59,7 +59,7 @@ describe("searchEntitiesWithExpiration", () => { number: 456, title: "Test PR", url: "https://github.com/test-owner/test-repo/pull/456", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -92,7 +92,7 @@ describe("searchEntitiesWithExpiration", () => { number: 789, title: "Test Discussion", url: "https://github.com/test-owner/test-repo/discussions/789", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -125,7 +125,7 @@ describe("searchEntitiesWithExpiration", () => { number: 1, title: "Agentic Issue", url: "https://github.com/test-owner/test-repo/issues/1", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -166,7 +166,7 @@ describe("searchEntitiesWithExpiration", () => { number: 1, title: "Issue with expiration", url: "https://github.com/test-owner/test-repo/issues/1", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -175,7 +175,7 @@ describe("searchEntitiesWithExpiration", () => { number: 2, title: "Issue without expiration", url: "https://github.com/test-owner/test-repo/issues/2", - body: "> AI generated by workflow\n\nNo expiration marker", + body: "> AI generated by workflow\n\nNo expiration marker\n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -207,7 +207,7 @@ describe("searchEntitiesWithExpiration", () => { number: 12667, title: "Legacy Format Issue", url: "https://github.com/test-owner/test-repo/issues/12667", - body: "> AI generated by workflow\n\n> - [x] expires on Jan 31, 2026, 6:04 AM UTC", + body: "> AI generated by workflow\n\n> - [x] expires on Jan 31, 2026, 6:04 AM UTC\n\n", createdAt: "2026-01-30T04:04:42Z", }; @@ -216,7 +216,7 @@ describe("searchEntitiesWithExpiration", () => { number: 123, title: "New Format Issue", url: "https://github.com/test-owner/test-repo/issues/123", - body: "> AI generated by workflow\n\n> - [x] expires on Dec 31, 2026, 11:59 PM UTC", + body: "> AI generated by workflow\n\n> - [x] expires on Dec 31, 2026, 11:59 PM UTC\n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -249,7 +249,7 @@ describe("searchEntitiesWithExpiration", () => { number: 1, title: "Issue 1", url: "https://github.com/test-owner/test-repo/issues/1", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -258,7 +258,7 @@ describe("searchEntitiesWithExpiration", () => { number: 2, title: "Issue 2", url: "https://github.com/test-owner/test-repo/issues/2", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -301,7 +301,7 @@ describe("searchEntitiesWithExpiration", () => { number: 1, title: "Duplicate Discussion", url: "https://github.com/test-owner/test-repo/discussions/1", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -346,7 +346,7 @@ describe("searchEntitiesWithExpiration", () => { number: 1, title: "Issue 1", url: "https://github.com/test-owner/test-repo/issues/1", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -355,7 +355,7 @@ describe("searchEntitiesWithExpiration", () => { number: 1, title: "Issue 1", url: "https://github.com/test-owner/test-repo/issues/1", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -441,7 +441,7 @@ describe("searchEntitiesWithExpiration", () => { number: 123, title: "Test Issue", url: "https://github.com/test-owner/test-repo/issues/123", - body: "> AI generated by workflow\n\n> - [x] expires ", + body: "> AI generated by workflow\n\n> - [x] expires \n\n", createdAt: "2026-01-01T00:00:00Z", }; @@ -468,4 +468,131 @@ describe("searchEntitiesWithExpiration", () => { expect(global.core.info).toHaveBeenCalledWith(expect.stringMatching(/Found issue #123 with expiration marker \((new|legacy) format\):/)); expect(global.core.info).toHaveBeenCalledWith("Search complete: Scanned 1 issues across 1 pages, found 1 with expiration markers"); }); + + it("should NOT find issue #12669 without XML workflow markers", async () => { + // Issue #12669: https://github.com/githubnext/gh-aw/issues/12669 + // This issue was not detected because it lacks XML comment markers + // (gh-aw-workflow-id or gh-aw-agentic-workflow) + // This is correct behavior - only issues with XML markers should be auto-closed + const issue12669 = { + id: "I_kwDOPc1QR87m5TZ3", + number: 12669, + title: "Smoke Copilot - Issue Group", + url: "https://github.com/githubnext/gh-aw/issues/12669", + body: `# Smoke Copilot + +Parent issue for grouping related issues from Smoke Copilot. + + + +Sub-issues are automatically linked below (max 64 per parent). + + +> Workflow: [Smoke Copilot]() +> - [x] expires on Jan 31, 2026, 6:05 AM UTC`, + createdAt: "2026-01-30T04:05:02Z", + }; + + mockGithub = { + graphql: vi.fn().mockResolvedValue({ + repository: { + issues: { + pageInfo: { hasNextPage: false, endCursor: null }, + nodes: [issue12669], + }, + }, + }), + }; + + const result = await searchEntitiesWithExpiration(mockGithub, owner, repo, { + entityType: "issues", + graphqlField: "issues", + resultKey: "issues", + }); + + // This issue should NOT be found because it lacks XML workflow markers + // Issues without markers are not managed by agentic workflows and shouldn't be auto-closed + expect(result.items).toHaveLength(0); + expect(result.stats.totalScanned).toBe(1); + }); + + it("should find issues with gh-aw-workflow-id XML marker", async () => { + const issueWithWorkflowId = { + id: "issue-with-id", + number: 12670, + title: "Test Issue with Workflow ID", + url: "https://github.com/test-owner/test-repo/issues/12670", + body: `# Test Issue + +Some content here + +> AI generated by [Smoke Copilot]() +> - [x] expires on Jan 31, 2026, 6:05 AM UTC + +`, + createdAt: "2026-01-30T04:05:02Z", + }; + + mockGithub = { + graphql: vi.fn().mockResolvedValue({ + repository: { + issues: { + pageInfo: { hasNextPage: false, endCursor: null }, + nodes: [issueWithWorkflowId], + }, + }, + }), + }; + + const result = await searchEntitiesWithExpiration(mockGithub, owner, repo, { + entityType: "issues", + graphqlField: "issues", + resultKey: "issues", + }); + + // This issue should be found because it has gh-aw-workflow-id marker + expect(result.items).toHaveLength(1); + expect(result.items[0]).toEqual(issueWithWorkflowId); + expect(result.stats.totalScanned).toBe(1); + }); + + it("should find issues with gh-aw-agentic-workflow XML marker", async () => { + const issueWithAgenticWorkflow = { + id: "issue-with-agentic", + number: 12671, + title: "Test Issue with Agentic Workflow", + url: "https://github.com/test-owner/test-repo/issues/12671", + body: `# Test Issue + +Content goes here + +> AI generated by [Test Workflow](https://example.com) +> - [x] expires on Feb 1, 2026, 10:00 AM UTC + +`, + createdAt: "2026-01-30T04:05:02Z", + }; + + mockGithub = { + graphql: vi.fn().mockResolvedValue({ + repository: { + issues: { + pageInfo: { hasNextPage: false, endCursor: null }, + nodes: [issueWithAgenticWorkflow], + }, + }, + }), + }; + + const result = await searchEntitiesWithExpiration(mockGithub, owner, repo, { + entityType: "issues", + graphqlField: "issues", + resultKey: "issues", + }); + + // This issue should be found because it has gh-aw-agentic-workflow marker + expect(result.items).toHaveLength(1); + expect(result.items[0]).toEqual(issueWithAgenticWorkflow); + expect(result.stats.totalScanned).toBe(1); + }); });