From b4eb49af771a93880ac142058355414dda6e4624 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 7 Dec 2025 21:53:21 +0000
Subject: [PATCH 1/4] Initial plan
From 0cf91964072ad97ee3b2e55d1b8e4c816030d0a8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 7 Dec 2025 22:01:56 +0000
Subject: [PATCH 2/4] Fix agentics-maintenance.yml syntax errors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Change /// to // on TypeScript reference directive (line 21)
- Fix indentation of entire JavaScript block to match YAML structure
- Remove trailing spaces on lines with 'comment {', 'id', 'url', 'discussion {'
- Remove extra blank line at end of file
- Verify workflow name exists: "Agentics Maintenance" ✓
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.github/workflows/agentics-maintenance.yml | 557 ++++++++++-----------
1 file changed, 278 insertions(+), 279 deletions(-)
diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml
index 978c48c15f..1112179462 100644
--- a/.github/workflows/agentics-maintenance.yml
+++ b/.github/workflows/agentics-maintenance.yml
@@ -18,285 +18,284 @@ jobs:
with:
script: |
// @ts-check
-///
-
-/**
- * Maximum number of discussions to update per run
- */
-const MAX_UPDATES_PER_RUN = 100;
-
-/**
- * Delay between GraphQL API calls in milliseconds to avoid rate limiting
- */
-const GRAPHQL_DELAY_MS = 500;
-
-/**
- * Delay execution for a specified number of milliseconds
- * @param {number} ms - Milliseconds to delay
- * @returns {Promise}
- */
-function delay(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
-}
-
-/**
- * Search for open discussions with expiration markers
- * @param {any} github - GitHub GraphQL instance
- * @param {string} owner - Repository owner
- * @param {string} repo - Repository name
- * @returns {Promise>} Matching discussions
- */
-async function searchDiscussionsWithExpiration(github, owner, repo) {
- const discussions = [];
- let hasNextPage = true;
- let cursor = null;
-
- while (hasNextPage) {
- const query = `
- query($owner: String!, $repo: String!, $cursor: String) {
- repository(owner: $owner, name: $repo) {
- discussions(first: 100, after: $cursor, states: [OPEN]) {
- pageInfo {
- hasNextPage
- endCursor
+ //
+
+ /**
+ * Maximum number of discussions to update per run
+ */
+ const MAX_UPDATES_PER_RUN = 100;
+
+ /**
+ * Delay between GraphQL API calls in milliseconds to avoid rate limiting
+ */
+ const GRAPHQL_DELAY_MS = 500;
+
+ /**
+ * Delay execution for a specified number of milliseconds
+ * @param {number} ms - Milliseconds to delay
+ * @returns {Promise}
+ */
+ function delay(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
}
- nodes {
- id
- number
- title
- url
- body
- createdAt
+
+ /**
+ * Search for open discussions with expiration markers
+ * @param {any} github - GitHub GraphQL instance
+ * @param {string} owner - Repository owner
+ * @param {string} repo - Repository name
+ * @returns {Promise>} Matching discussions
+ */
+ async function searchDiscussionsWithExpiration(github, owner, repo) {
+ const discussions = [];
+ let hasNextPage = true;
+ let cursor = null;
+
+ while (hasNextPage) {
+ const query = `
+ query($owner: String!, $repo: String!, $cursor: String) {
+ repository(owner: $owner, name: $repo) {
+ discussions(first: 100, after: $cursor, states: [OPEN]) {
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ nodes {
+ id
+ number
+ title
+ url
+ body
+ createdAt
+ }
+ }
+ }
+ }
+ `;
+
+ const result = await github.graphql(query, {
+ owner: owner,
+ repo: repo,
+ cursor: cursor,
+ });
+
+ if (!result || !result.repository || !result.repository.discussions) {
+ break;
+ }
+
+ const nodes = result.repository.discussions.nodes || [];
+
+ // Filter for discussions with agentic workflow markers and expiration comments
+ for (const discussion 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 = discussion.body && agenticPattern.test(discussion.body);
+
+ if (!isAgenticWorkflow) {
+ continue;
+ }
+
+ // Check if has expiration marker
+ const expirationPattern = //;
+ const match = discussion.body ? discussion.body.match(expirationPattern) : null;
+
+ if (match) {
+ discussions.push(discussion);
+ }
+ }
+
+ hasNextPage = result.repository.discussions.pageInfo.hasNextPage;
+ cursor = result.repository.discussions.pageInfo.endCursor;
+ }
+
+ return discussions;
+ }
+
+ /**
+ * Extract expiration date from discussion body
+ * @param {string} body - Discussion body
+ * @returns {Date|null} Expiration date or null if not found/invalid
+ */
+ function extractExpirationDate(body) {
+ const expirationPattern = //;
+ const match = body.match(expirationPattern);
+
+ if (!match) {
+ return null;
+ }
+
+ const expirationISO = match[1].trim();
+ const expirationDate = new Date(expirationISO);
+
+ // Validate the date
+ if (isNaN(expirationDate.getTime())) {
+ return null;
+ }
+
+ return expirationDate;
+ }
+
+ /**
+ * Validate discussion creation date
+ * @param {string} createdAt - ISO 8601 creation date
+ * @returns {boolean} True if valid
+ */
+ function validateCreationDate(createdAt) {
+ const creationDate = new Date(createdAt);
+ return !isNaN(creationDate.getTime());
+ }
+
+ /**
+ * Add comment to a GitHub Discussion using GraphQL
+ * @param {any} github - GitHub GraphQL instance
+ * @param {string} discussionId - Discussion node ID
+ * @param {string} message - Comment body
+ * @returns {Promise<{id: string, url: string}>} Comment details
+ */
+ async function addDiscussionComment(github, discussionId, message) {
+ const result = await github.graphql(
+ `
+ mutation($dId: ID!, $body: String!) {
+ addDiscussionComment(input: { discussionId: $dId, body: $body }) {
+ comment {
+ id
+ url
+ }
+ }
+ }`,
+ { dId: discussionId, body: message }
+ );
+
+ return result.addDiscussionComment.comment;
+ }
+
+ /**
+ * Close a GitHub Discussion as OUTDATED using GraphQL
+ * @param {any} github - GitHub GraphQL instance
+ * @param {string} discussionId - Discussion node ID
+ * @returns {Promise<{id: string, url: string}>} Discussion details
+ */
+ async function closeDiscussionAsOutdated(github, discussionId) {
+ const result = await github.graphql(
+ `
+ mutation($dId: ID!) {
+ closeDiscussion(input: { discussionId: $dId, reason: OUTDATED }) {
+ discussion {
+ id
+ url
+ }
+ }
+ }`,
+ { dId: discussionId }
+ );
+
+ return result.closeDiscussion.discussion;
+ }
+
+ async function main() {
+ const owner = context.repo.owner;
+ const repo = context.repo.repo;
+
+ core.info(`Searching for expired discussions in ${owner}/${repo}`);
+
+ // Search for discussions with expiration markers
+ const discussionsWithExpiration = await searchDiscussionsWithExpiration(github, owner, repo);
+
+ if (discussionsWithExpiration.length === 0) {
+ core.info("No discussions with expiration markers found");
+ return;
+ }
+
+ core.info(`Found ${discussionsWithExpiration.length} discussion(s) with expiration markers`);
+
+ // Check which discussions are expired
+ const now = new Date();
+ const expiredDiscussions = [];
+
+ for (const discussion of discussionsWithExpiration) {
+ // Validate creation date
+ if (!validateCreationDate(discussion.createdAt)) {
+ core.warning(`Discussion #${discussion.number} has invalid creation date, skipping`);
+ continue;
+ }
+
+ // Extract and validate expiration date
+ const expirationDate = extractExpirationDate(discussion.body);
+ if (!expirationDate) {
+ core.warning(`Discussion #${discussion.number} has invalid expiration date, skipping`);
+ continue;
+ }
+
+ // Check if expired
+ if (now >= expirationDate) {
+ expiredDiscussions.push({
+ ...discussion,
+ expirationDate: expirationDate,
+ });
+ }
+ }
+
+ if (expiredDiscussions.length === 0) {
+ core.info("No expired discussions found");
+ return;
+ }
+
+ core.info(`Found ${expiredDiscussions.length} expired discussion(s)`);
+
+ // Limit to MAX_UPDATES_PER_RUN
+ const discussionsToClose = expiredDiscussions.slice(0, MAX_UPDATES_PER_RUN);
+
+ if (expiredDiscussions.length > MAX_UPDATES_PER_RUN) {
+ core.warning(`Found ${expiredDiscussions.length} expired discussions, but only closing the first ${MAX_UPDATES_PER_RUN}`);
+ }
+
+ let closedCount = 0;
+ const closedDiscussions = [];
+
+ for (let i = 0; i < discussionsToClose.length; i++) {
+ const discussion = discussionsToClose[i];
+
+ try {
+ const closingMessage = `This discussion was automatically closed because it expired on ${discussion.expirationDate.toISOString()}.`;
+
+ // Add comment first
+ core.info(`Adding closing comment to discussion #${discussion.number}`);
+ await addDiscussionComment(github, discussion.id, closingMessage);
+
+ // Then close the discussion as outdated
+ core.info(`Closing discussion #${discussion.number} as outdated`);
+ await closeDiscussionAsOutdated(github, discussion.id);
+
+ closedDiscussions.push({
+ number: discussion.number,
+ url: discussion.url,
+ title: discussion.title,
+ });
+
+ closedCount++;
+ core.info(`✓ Closed discussion #${discussion.number}: ${discussion.url}`);
+ } catch (error) {
+ core.error(`✗ Failed to close discussion #${discussion.number}: ${error instanceof Error ? error.message : String(error)}`);
+ // Continue with other discussions even if one fails
+ }
+
+ // Add delay between GraphQL operations to avoid rate limiting (except for the last item)
+ if (i < discussionsToClose.length - 1) {
+ await delay(GRAPHQL_DELAY_MS);
+ }
+ }
+
+ // Write summary
+ if (closedCount > 0) {
+ let summaryContent = `## Closed Expired Discussions\n\n`;
+ summaryContent += `Closed **${closedCount}** expired discussion(s):\n\n`;
+ for (const closed of closedDiscussions) {
+ summaryContent += `- Discussion #${closed.number}: [${closed.title}](${closed.url})\n`;
+ }
+ await core.summary.addRaw(summaryContent).write();
+ }
+
+ core.info(`Successfully closed ${closedCount} expired discussion(s)`);
}
- }
- }
- }
- `;
-
- const result = await github.graphql(query, {
- owner: owner,
- repo: repo,
- cursor: cursor,
- });
-
- if (!result || !result.repository || !result.repository.discussions) {
- break;
- }
-
- const nodes = result.repository.discussions.nodes || [];
-
- // Filter for discussions with agentic workflow markers and expiration comments
- for (const discussion 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 = discussion.body && agenticPattern.test(discussion.body);
-
- if (!isAgenticWorkflow) {
- continue;
- }
-
- // Check if has expiration marker
- const expirationPattern = //;
- const match = discussion.body ? discussion.body.match(expirationPattern) : null;
-
- if (match) {
- discussions.push(discussion);
- }
- }
-
- hasNextPage = result.repository.discussions.pageInfo.hasNextPage;
- cursor = result.repository.discussions.pageInfo.endCursor;
- }
-
- return discussions;
-}
-
-/**
- * Extract expiration date from discussion body
- * @param {string} body - Discussion body
- * @returns {Date|null} Expiration date or null if not found/invalid
- */
-function extractExpirationDate(body) {
- const expirationPattern = //;
- const match = body.match(expirationPattern);
-
- if (!match) {
- return null;
- }
-
- const expirationISO = match[1].trim();
- const expirationDate = new Date(expirationISO);
-
- // Validate the date
- if (isNaN(expirationDate.getTime())) {
- return null;
- }
-
- return expirationDate;
-}
-
-/**
- * Validate discussion creation date
- * @param {string} createdAt - ISO 8601 creation date
- * @returns {boolean} True if valid
- */
-function validateCreationDate(createdAt) {
- const creationDate = new Date(createdAt);
- return !isNaN(creationDate.getTime());
-}
-
-/**
- * Add comment to a GitHub Discussion using GraphQL
- * @param {any} github - GitHub GraphQL instance
- * @param {string} discussionId - Discussion node ID
- * @param {string} message - Comment body
- * @returns {Promise<{id: string, url: string}>} Comment details
- */
-async function addDiscussionComment(github, discussionId, message) {
- const result = await github.graphql(
- `
- mutation($dId: ID!, $body: String!) {
- addDiscussionComment(input: { discussionId: $dId, body: $body }) {
- comment {
- id
- url
- }
- }
- }`,
- { dId: discussionId, body: message }
- );
-
- return result.addDiscussionComment.comment;
-}
-
-/**
- * Close a GitHub Discussion as OUTDATED using GraphQL
- * @param {any} github - GitHub GraphQL instance
- * @param {string} discussionId - Discussion node ID
- * @returns {Promise<{id: string, url: string}>} Discussion details
- */
-async function closeDiscussionAsOutdated(github, discussionId) {
- const result = await github.graphql(
- `
- mutation($dId: ID!) {
- closeDiscussion(input: { discussionId: $dId, reason: OUTDATED }) {
- discussion {
- id
- url
- }
- }
- }`,
- { dId: discussionId }
- );
-
- return result.closeDiscussion.discussion;
-}
-
-async function main() {
- const owner = context.repo.owner;
- const repo = context.repo.repo;
-
- core.info(`Searching for expired discussions in ${owner}/${repo}`);
-
- // Search for discussions with expiration markers
- const discussionsWithExpiration = await searchDiscussionsWithExpiration(github, owner, repo);
-
- if (discussionsWithExpiration.length === 0) {
- core.info("No discussions with expiration markers found");
- return;
- }
-
- core.info(`Found ${discussionsWithExpiration.length} discussion(s) with expiration markers`);
-
- // Check which discussions are expired
- const now = new Date();
- const expiredDiscussions = [];
-
- for (const discussion of discussionsWithExpiration) {
- // Validate creation date
- if (!validateCreationDate(discussion.createdAt)) {
- core.warning(`Discussion #${discussion.number} has invalid creation date, skipping`);
- continue;
- }
-
- // Extract and validate expiration date
- const expirationDate = extractExpirationDate(discussion.body);
- if (!expirationDate) {
- core.warning(`Discussion #${discussion.number} has invalid expiration date, skipping`);
- continue;
- }
-
- // Check if expired
- if (now >= expirationDate) {
- expiredDiscussions.push({
- ...discussion,
- expirationDate: expirationDate,
- });
- }
- }
-
- if (expiredDiscussions.length === 0) {
- core.info("No expired discussions found");
- return;
- }
-
- core.info(`Found ${expiredDiscussions.length} expired discussion(s)`);
-
- // Limit to MAX_UPDATES_PER_RUN
- const discussionsToClose = expiredDiscussions.slice(0, MAX_UPDATES_PER_RUN);
-
- if (expiredDiscussions.length > MAX_UPDATES_PER_RUN) {
- core.warning(`Found ${expiredDiscussions.length} expired discussions, but only closing the first ${MAX_UPDATES_PER_RUN}`);
- }
-
- let closedCount = 0;
- const closedDiscussions = [];
-
- for (let i = 0; i < discussionsToClose.length; i++) {
- const discussion = discussionsToClose[i];
-
- try {
- const closingMessage = `This discussion was automatically closed because it expired on ${discussion.expirationDate.toISOString()}.`;
-
- // Add comment first
- core.info(`Adding closing comment to discussion #${discussion.number}`);
- await addDiscussionComment(github, discussion.id, closingMessage);
-
- // Then close the discussion as outdated
- core.info(`Closing discussion #${discussion.number} as outdated`);
- await closeDiscussionAsOutdated(github, discussion.id);
-
- closedDiscussions.push({
- number: discussion.number,
- url: discussion.url,
- title: discussion.title,
- });
-
- closedCount++;
- core.info(`✓ Closed discussion #${discussion.number}: ${discussion.url}`);
- } catch (error) {
- core.error(`✗ Failed to close discussion #${discussion.number}: ${error instanceof Error ? error.message : String(error)}`);
- // Continue with other discussions even if one fails
- }
-
- // Add delay between GraphQL operations to avoid rate limiting (except for the last item)
- if (i < discussionsToClose.length - 1) {
- await delay(GRAPHQL_DELAY_MS);
- }
- }
-
- // Write summary
- if (closedCount > 0) {
- let summaryContent = `## Closed Expired Discussions\n\n`;
- summaryContent += `Closed **${closedCount}** expired discussion(s):\n\n`;
- for (const closed of closedDiscussions) {
- summaryContent += `- Discussion #${closed.number}: [${closed.title}](${closed.url})\n`;
- }
- await core.summary.addRaw(summaryContent).write();
- }
-
- core.info(`Successfully closed ${closedCount} expired discussion(s)`);
-}
-
-await main();
+ await main();
\ No newline at end of file
From 25c78ee45893bbefbbbf74bfb3ca08098dfe477f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 7 Dec 2025 22:03:39 +0000
Subject: [PATCH 3/4] Fix comment indentation in agentics-maintenance.yml
Fix excessive indentation (24 spaces) on first JSDoc comment block to match
the consistent 12-space indentation used throughout the JavaScript code.
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.github/workflows/agentics-maintenance.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml
index 1112179462..169e62ee5d 100644
--- a/.github/workflows/agentics-maintenance.yml
+++ b/.github/workflows/agentics-maintenance.yml
@@ -20,7 +20,7 @@ jobs:
// @ts-check
//
- /**
+ /**
* Maximum number of discussions to update per run
*/
const MAX_UPDATES_PER_RUN = 100;
From ed8ffad28440adc2cd99800d2ab40fe8c739a46a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 7 Dec 2025 22:20:55 +0000
Subject: [PATCH 4/4] Fix agentics-maintenance.yml generation using bundler
- Changed /// to // in close_expired_discussions.cjs source file
- Updated maintenance_workflow.go to use WriteJavaScriptToYAML helper
- This properly formats JS with indentation and removes comments
- Generated workflow now has valid YAML syntax
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.github/workflows/agentics-maintenance.yml | 114 +-----------------
.github/workflows/release.lock.yml | 6 +-
pkg/workflow/js/close_expired_discussions.cjs | 2 +-
pkg/workflow/maintenance_workflow.go | 17 ++-
4 files changed, 20 insertions(+), 119 deletions(-)
diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml
index 169e62ee5d..bcebc75c79 100644
--- a/.github/workflows/agentics-maintenance.yml
+++ b/.github/workflows/agentics-maintenance.yml
@@ -17,40 +17,15 @@ jobs:
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
- // @ts-check
- //
-
- /**
- * Maximum number of discussions to update per run
- */
const MAX_UPDATES_PER_RUN = 100;
-
- /**
- * Delay between GraphQL API calls in milliseconds to avoid rate limiting
- */
const GRAPHQL_DELAY_MS = 500;
-
- /**
- * Delay execution for a specified number of milliseconds
- * @param {number} ms - Milliseconds to delay
- * @returns {Promise}
- */
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
-
- /**
- * Search for open discussions with expiration markers
- * @param {any} github - GitHub GraphQL instance
- * @param {string} owner - Repository owner
- * @param {string} repo - Repository name
- * @returns {Promise>} Matching discussions
- */
async function searchDiscussionsWithExpiration(github, owner, repo) {
const discussions = [];
let hasNextPage = true;
let cursor = null;
-
while (hasNextPage) {
const query = `
query($owner: String!, $repo: String!, $cursor: String) {
@@ -72,115 +47,70 @@ jobs:
}
}
`;
-
const result = await github.graphql(query, {
owner: owner,
repo: repo,
cursor: cursor,
});
-
if (!result || !result.repository || !result.repository.discussions) {
break;
}
-
const nodes = result.repository.discussions.nodes || [];
-
- // Filter for discussions with agentic workflow markers and expiration comments
for (const discussion 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 = discussion.body && agenticPattern.test(discussion.body);
-
if (!isAgenticWorkflow) {
continue;
}
-
- // Check if has expiration marker
const expirationPattern = //;
const match = discussion.body ? discussion.body.match(expirationPattern) : null;
-
if (match) {
discussions.push(discussion);
}
}
-
hasNextPage = result.repository.discussions.pageInfo.hasNextPage;
cursor = result.repository.discussions.pageInfo.endCursor;
}
-
return discussions;
}
-
- /**
- * Extract expiration date from discussion body
- * @param {string} body - Discussion body
- * @returns {Date|null} Expiration date or null if not found/invalid
- */
function extractExpirationDate(body) {
const expirationPattern = //;
const match = body.match(expirationPattern);
-
if (!match) {
return null;
}
-
const expirationISO = match[1].trim();
const expirationDate = new Date(expirationISO);
-
- // Validate the date
if (isNaN(expirationDate.getTime())) {
return null;
}
-
return expirationDate;
}
-
- /**
- * Validate discussion creation date
- * @param {string} createdAt - ISO 8601 creation date
- * @returns {boolean} True if valid
- */
function validateCreationDate(createdAt) {
const creationDate = new Date(createdAt);
return !isNaN(creationDate.getTime());
}
-
- /**
- * Add comment to a GitHub Discussion using GraphQL
- * @param {any} github - GitHub GraphQL instance
- * @param {string} discussionId - Discussion node ID
- * @param {string} message - Comment body
- * @returns {Promise<{id: string, url: string}>} Comment details
- */
async function addDiscussionComment(github, discussionId, message) {
const result = await github.graphql(
`
mutation($dId: ID!, $body: String!) {
addDiscussionComment(input: { discussionId: $dId, body: $body }) {
- comment {
- id
+ comment {
+ id
url
}
}
}`,
{ dId: discussionId, body: message }
);
-
return result.addDiscussionComment.comment;
}
-
- /**
- * Close a GitHub Discussion as OUTDATED using GraphQL
- * @param {any} github - GitHub GraphQL instance
- * @param {string} discussionId - Discussion node ID
- * @returns {Promise<{id: string, url: string}>} Discussion details
- */
async function closeDiscussionAsOutdated(github, discussionId) {
const result = await github.graphql(
`
mutation($dId: ID!) {
closeDiscussion(input: { discussionId: $dId, reason: OUTDATED }) {
- discussion {
+ discussion {
id
url
}
@@ -188,45 +118,30 @@ jobs:
}`,
{ dId: discussionId }
);
-
return result.closeDiscussion.discussion;
}
-
async function main() {
const owner = context.repo.owner;
const repo = context.repo.repo;
-
core.info(`Searching for expired discussions in ${owner}/${repo}`);
-
- // Search for discussions with expiration markers
const discussionsWithExpiration = await searchDiscussionsWithExpiration(github, owner, repo);
-
if (discussionsWithExpiration.length === 0) {
core.info("No discussions with expiration markers found");
return;
}
-
core.info(`Found ${discussionsWithExpiration.length} discussion(s) with expiration markers`);
-
- // Check which discussions are expired
const now = new Date();
const expiredDiscussions = [];
-
for (const discussion of discussionsWithExpiration) {
- // Validate creation date
if (!validateCreationDate(discussion.createdAt)) {
core.warning(`Discussion #${discussion.number} has invalid creation date, skipping`);
continue;
}
-
- // Extract and validate expiration date
const expirationDate = extractExpirationDate(discussion.body);
if (!expirationDate) {
core.warning(`Discussion #${discussion.number} has invalid expiration date, skipping`);
continue;
}
-
- // Check if expired
if (now >= expirationDate) {
expiredDiscussions.push({
...discussion,
@@ -234,58 +149,39 @@ jobs:
});
}
}
-
if (expiredDiscussions.length === 0) {
core.info("No expired discussions found");
return;
}
-
core.info(`Found ${expiredDiscussions.length} expired discussion(s)`);
-
- // Limit to MAX_UPDATES_PER_RUN
const discussionsToClose = expiredDiscussions.slice(0, MAX_UPDATES_PER_RUN);
-
if (expiredDiscussions.length > MAX_UPDATES_PER_RUN) {
core.warning(`Found ${expiredDiscussions.length} expired discussions, but only closing the first ${MAX_UPDATES_PER_RUN}`);
}
-
let closedCount = 0;
const closedDiscussions = [];
-
for (let i = 0; i < discussionsToClose.length; i++) {
const discussion = discussionsToClose[i];
-
try {
const closingMessage = `This discussion was automatically closed because it expired on ${discussion.expirationDate.toISOString()}.`;
-
- // Add comment first
core.info(`Adding closing comment to discussion #${discussion.number}`);
await addDiscussionComment(github, discussion.id, closingMessage);
-
- // Then close the discussion as outdated
core.info(`Closing discussion #${discussion.number} as outdated`);
await closeDiscussionAsOutdated(github, discussion.id);
-
closedDiscussions.push({
number: discussion.number,
url: discussion.url,
title: discussion.title,
});
-
closedCount++;
core.info(`✓ Closed discussion #${discussion.number}: ${discussion.url}`);
} catch (error) {
core.error(`✗ Failed to close discussion #${discussion.number}: ${error instanceof Error ? error.message : String(error)}`);
- // Continue with other discussions even if one fails
}
-
- // Add delay between GraphQL operations to avoid rate limiting (except for the last item)
if (i < discussionsToClose.length - 1) {
await delay(GRAPHQL_DELAY_MS);
}
}
-
- // Write summary
if (closedCount > 0) {
let summaryContent = `## Closed Expired Discussions\n\n`;
summaryContent += `Closed **${closedCount}** expired discussion(s):\n\n`;
@@ -294,8 +190,6 @@ jobs:
}
await core.summary.addRaw(summaryContent).write();
}
-
core.info(`Successfully closed ${closedCount} expired discussion(s)`);
}
-
- await main();
\ No newline at end of file
+ await main();
diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml
index 160a26701e..a73e060474 100644
--- a/.github/workflows/release.lock.yml
+++ b/.github/workflows/release.lock.yml
@@ -6119,19 +6119,19 @@ jobs:
- name: Download Go modules
run: go mod download
- name: Generate SBOM (SPDX format)
- uses: anchore/sbom-action@fbfd9c6c189226748411491745178e0c2017392d # v0
+ uses: anchore/sbom-action@fbfd9c6c189226748411491745178e0c2017392d # v0.20.10
with:
artifact-name: sbom.spdx.json
format: spdx-json
output-file: sbom.spdx.json
- name: Generate SBOM (CycloneDX format)
- uses: anchore/sbom-action@fbfd9c6c189226748411491745178e0c2017392d # v0
+ uses: anchore/sbom-action@fbfd9c6c189226748411491745178e0c2017392d # v0.20.10
with:
artifact-name: sbom.cdx.json
format: cyclonedx-json
output-file: sbom.cdx.json
- name: Upload SBOM artifacts
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: sbom-artifacts
path: |
diff --git a/pkg/workflow/js/close_expired_discussions.cjs b/pkg/workflow/js/close_expired_discussions.cjs
index 7af39b7a6c..8cf285b39d 100644
--- a/pkg/workflow/js/close_expired_discussions.cjs
+++ b/pkg/workflow/js/close_expired_discussions.cjs
@@ -1,5 +1,5 @@
// @ts-check
-///
+//
/**
* Maximum number of discussions to update per run
diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go
index 86d57e164f..eb1cc910a2 100644
--- a/pkg/workflow/maintenance_workflow.go
+++ b/pkg/workflow/maintenance_workflow.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "strings"
"github.com/githubnext/gh-aw/pkg/logger"
)
@@ -34,9 +35,10 @@ func GenerateMaintenanceWorkflow(workflowDataList []*WorkflowData, workflowDir s
maintenanceLog.Print("Generating maintenance workflow for expired discussions")
- // Create the maintenance workflow content
- script := getMaintenanceScript()
- content := fmt.Sprintf(`name: Agentics Maintenance
+ // Create the maintenance workflow content using strings.Builder
+ var yaml strings.Builder
+
+ yaml.WriteString(`name: Agentics Maintenance
on:
schedule:
@@ -55,8 +57,13 @@ jobs:
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
- %s
-`, script)
+`)
+
+ // Add the JavaScript script with proper indentation
+ script := getMaintenanceScript()
+ WriteJavaScriptToYAML(&yaml, script)
+
+ content := yaml.String()
// Write the maintenance workflow file
maintenanceFile := filepath.Join(workflowDir, "agentics-maintenance.yml")