Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions actions/setup/js/expired_entity_search_helpers.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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. <!-- gh-aw-workflow-id: ... --> (standalone workflow ID marker)
// 2. <!-- gh-aw-agentic-workflow: ... --> (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++;
Expand Down
155 changes: 141 additions & 14 deletions actions/setup/js/expired_entity_search_helpers.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand Down Expand Up @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand Down Expand Up @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand Down Expand Up @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand Down Expand Up @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand All @@ -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<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand Down Expand Up @@ -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<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-30T04:04:42Z",
};

Expand All @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z --> on Dec 31, 2026, 11:59 PM UTC",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z --> on Dec 31, 2026, 11:59 PM UTC\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand Down Expand Up @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand All @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand Down Expand Up @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand Down Expand Up @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand All @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand Down Expand Up @@ -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 <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->",
body: "> AI generated by workflow\n\n> - [x] expires <!-- gh-aw-expires: 2026-12-31T23:59:59Z -->\n\n<!-- gh-aw-workflow-id: test-workflow -->",
createdAt: "2026-01-01T00:00:00Z",
};

Expand All @@ -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 <a>Smoke Copilot</a>.



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

<!-- gh-aw-workflow-id: smoke-copilot -->`,
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

<!-- gh-aw-agentic-workflow: Test Workflow, run: https://example.com -->`,
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);
});
});
Loading