From 815ee7b809530378488610cf36b3b23e69bed3af Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Apr 2026 03:09:48 +0000 Subject: [PATCH] chore: sync workflow templates from Workflows repo Automated sync from stranske/Workflows Template hash: dcdd58751759 Changes synced from sync-manifest.yml --- .github/scripts/agents_pr_meta_keepalive.js | 9 ++ .github/scripts/agents_pr_meta_update_body.js | 30 ++++- .github/scripts/coverage_monitor_summary.js | 8 +- .github/scripts/source_context.js | 103 ++++++++++++++---- scripts/aggregate_agent_metrics.py | 10 +- 5 files changed, 135 insertions(+), 25 deletions(-) diff --git a/.github/scripts/agents_pr_meta_keepalive.js b/.github/scripts/agents_pr_meta_keepalive.js index 39eaec8ae..2e241bf4f 100644 --- a/.github/scripts/agents_pr_meta_keepalive.js +++ b/.github/scripts/agents_pr_meta_keepalive.js @@ -755,6 +755,15 @@ async function detectKeepalive({ core, github, context, env = process.env }) { return finalise(); } + if (sourceContext.noAutomation) { + outputs.reason = 'no-automation-source-context'; + outputs.dispatch = 'false'; + core.info( + `Keepalive dispatch skipped: PR source context opts out of automation (${formatSourceContextForLog(sourceContext)}).`, + ); + return finalise(); + } + if (!issueNumber) { if (sourceContext.isValid && !sourceContext.requiresIssue) { core.info( diff --git a/.github/scripts/agents_pr_meta_update_body.js b/.github/scripts/agents_pr_meta_update_body.js index 89c24c55d..0416293a4 100644 --- a/.github/scripts/agents_pr_meta_update_body.js +++ b/.github/scripts/agents_pr_meta_update_body.js @@ -1023,6 +1023,10 @@ function resolveExplicitNonIssueWorkflowSourceContext(pr = {}) { }; } +function resolveNonIssueWorkflowSourceContextForBodySync(pr = {}, issueNumber = null) { + return issueNumber ? null : resolveExplicitNonIssueWorkflowSourceContext(pr); +} + async function resolveSourceContextRepairComment({ github, owner, @@ -1356,7 +1360,9 @@ async function run({github: rawGithub, context, core, inputs}) { return; } - const explicitNonIssueSourceContext = resolveExplicitNonIssueWorkflowSourceContext(pr); + const issueNumber = extractIssueNumberFromPull(pr); + const sourceContext = resolvePrSourceContext(pr); + const explicitNonIssueSourceContext = resolveNonIssueWorkflowSourceContextForBodySync(pr, issueNumber); if (explicitNonIssueSourceContext) { core.info( `PR #${pr.number} has explicit non-issue workflow source context (${formatSourceContextForLog(explicitNonIssueSourceContext)}); skipping issue-sourced body sync.`, @@ -1382,8 +1388,6 @@ async function run({github: rawGithub, context, core, inputs}) { return; } - const issueNumber = extractIssueNumberFromPull(pr); - const sourceContext = resolvePrSourceContext(pr); if (!issueNumber) { if (sourceContext.isValid && !sourceContext.requiresIssue) { core.info( @@ -1454,6 +1458,25 @@ async function run({github: rawGithub, context, core, inputs}) { return; } + try { + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: pr.number, + }); + await resolveSourceContextRepairComment({ + github, + owner, + repo, + prNumber: pr.number, + comments, + sourceContext, + core, + }); + } catch (error) { + core.warning(`Failed to resolve workflow source repair comment: ${error.message}`); + } + core.info(`Fetching content from issue #${issueNumber} for PR #${pr.number}`); const issueResponse = await withRetries( () => github.rest.issues.get({owner, repo, issue_number: issueNumber}), @@ -1629,6 +1652,7 @@ module.exports = { buildSourceContextRepairCommentBody, buildSourceContextResolvedCommentBody, resolveExplicitNonIssueWorkflowSourceContext, + resolveNonIssueWorkflowSourceContextForBodySync, resolveSourceContextRepairComment, isCampaignIssue, buildStatusBlock, diff --git a/.github/scripts/coverage_monitor_summary.js b/.github/scripts/coverage_monitor_summary.js index 0a2fa7565..67ef84d76 100644 --- a/.github/scripts/coverage_monitor_summary.js +++ b/.github/scripts/coverage_monitor_summary.js @@ -140,8 +140,12 @@ function optionalExistingReportPath(filePath) { const cleanedPath = cleanString(filePath); if (!cleanedPath) return ''; if (!fs.existsSync(cleanedPath)) return ''; - if (!fs.statSync(cleanedPath).isFile()) return ''; - return cleanedPath; + try { + if (!fs.statSync(cleanedPath).isFile()) return ''; + return cleanedPath; + } catch (_error) { + return ''; + } } function buildCoverageMonitorSummary(options = {}) { diff --git a/.github/scripts/source_context.js b/.github/scripts/source_context.js index b7899253a..78016de2f 100644 --- a/.github/scripts/source_context.js +++ b/.github/scripts/source_context.js @@ -34,19 +34,23 @@ const SOURCE_LABELS = Object.freeze({ workflow_no_automation: SOURCE_TYPES.MANUAL_REMOTE, }); +const NO_AUTOMATION_LABELS = new Set(['workflow:no-automation', 'workflow_no_automation']); + const CHECKBOX_SOURCE_PATTERNS = Object.freeze([ [SOURCE_TYPES.GITHUB_ISSUE, /\bgithub\s+issue\b|\bsource\s+issue\b/i], [ SOURCE_TYPES.MANUAL_REMOTE, /\bdirect\s+pr\b|\bremote\s+github\s+work\b|\bstarted\s+directly\b|\bdo\s+not\s+automate\b|\bhuman[- ]only\b/i, ], - [SOURCE_TYPES.LOCAL_REQUEST, /\blocal\s+(?:codex|user)\s+request\b|\blocal\s+request\b/i], + [SOURCE_TYPES.LOCAL_REQUEST, /\blocal\s+(?:codex(?:\s*\/\s*|\s+)?user|codex|user)\s+request\b|\blocal\s+request\b/i], [SOURCE_TYPES.AUTOMATION_RUN, /\bautomation\s+run\b|\bworkflow\s+run\b/i], [SOURCE_TYPES.REVIEW_FOLLOWUP, /\breview\s+follow[- ]?up\b|\bfollow[- ]?up\s+from\s+pr\b/i], [SOURCE_TYPES.SYNC_CAMPAIGN, /\bsync\b|\bmaintenance\s+campaign\b|\bmaintenance\b/i], [SOURCE_TYPES.DEPENDABOT, /\bdependabot\b|\bdependency\s+update\b/i], ]); +const NO_AUTOMATION_CHECKBOX_PATTERN = /\bdo\s+not\s+automate\b|\bhuman[- ]only\b/i; + function cleanString(value) { return String(value || '').trim(); } @@ -106,6 +110,54 @@ function labelNames(pull = {}) { : []; } +function checkedLabels(lines) { + return lines + .map((line) => line.match(/^\s*[-*]\s+\[[xX]\]\s+(.+?)\s*$/)) + .filter(Boolean) + .map((match) => match[1]); +} + +function workflowSourceSectionLines(body) { + const lines = String(body || '').split(/\r?\n/); + const start = lines.findIndex((line) => /^#{1,6}\s+Workflow Source\s*$/i.test(line)); + if (start < 0) { + return []; + } + + const sectionLines = []; + for (const line of lines.slice(start + 1)) { + if (/^#{1,6}\s+\S/.test(line)) { + break; + } + sectionLines.push(line); + } + return sectionLines; +} + +function startedFromLines(sectionLines) { + const start = sectionLines.findIndex((line) => /^\s*Started from:\s*$/i.test(line)); + if (start < 0) { + return sectionLines; + } + + const result = []; + for (const line of sectionLines.slice(start + 1)) { + if (/^\s*(Automation intent|Notes):\s*$/i.test(line)) { + break; + } + result.push(line); + } + return result; +} + +function hasCheckedNoAutomationTemplate(body) { + const sectionLines = workflowSourceSectionLines(body); + if (!sectionLines.length) { + return false; + } + return checkedLabels(sectionLines).some((label) => NO_AUTOMATION_CHECKBOX_PATTERN.test(label)); +} + function hasExplicitIssueReferencePrefix(value) { const prefix = cleanString(value) .replace(/[>_[\]()`*~]/g, ' ') @@ -200,26 +252,12 @@ function parseWorkflowSourceBlock(body) { } function sourceTypeFromCheckedTemplate(body) { - const lines = String(body || '').split(/\r?\n/); - const start = lines.findIndex((line) => /^#{1,6}\s+Workflow Source\s*$/i.test(line)); - if (start < 0) { + const sectionLines = workflowSourceSectionLines(body); + if (!sectionLines.length) { return SOURCE_TYPES.UNKNOWN; } - const sectionLines = []; - for (const line of lines.slice(start + 1)) { - if (/^#{1,6}\s+\S/.test(line)) { - break; - } - sectionLines.push(line); - } - const text = sectionLines.join('\n'); const checkedTypes = new Set(); - for (const line of text.split(/\r?\n/)) { - const checkbox = line.match(/^\s*[-*]\s+\[[xX]\]\s+(.+?)\s*$/); - if (!checkbox) { - continue; - } - const label = checkbox[1]; + for (const label of checkedLabels(startedFromLines(sectionLines))) { for (const [sourceType, pattern] of CHECKBOX_SOURCE_PATTERNS) { if (pattern.test(label)) { checkedTypes.add(sourceType); @@ -230,6 +268,27 @@ function sourceTypeFromCheckedTemplate(body) { return checkedTypes.size === 1 ? Array.from(checkedTypes)[0] : SOURCE_TYPES.UNKNOWN; } +function hasNoAutomationWorkflowContext(pull = {}) { + const body = String(pull?.body || ''); + const markerToken = normalizeToken(parseHtmlMarker(body, 'workflow-source')); + const block = parseWorkflowSourceBlock(body); + const blockTokens = [ + block.origin, + block.source, + block.type, + block.automation, + block.automation_intent, + ].map(normalizeToken); + const labels = labelNames(pull).map((label) => label.toLowerCase()); + + return ( + markerToken === 'no_automation' + || blockTokens.includes('no_automation') + || labels.some((label) => NO_AUTOMATION_LABELS.has(label) || NO_AUTOMATION_LABELS.has(normalizeToken(label))) + || hasCheckedNoAutomationTemplate(body) + ); +} + function sourceTypeFromLabels(pull = {}) { for (const label of labelNames(pull)) { const sourceType = SOURCE_LABELS[label.toLowerCase()] || SOURCE_LABELS[normalizeToken(label)]; @@ -267,6 +326,7 @@ function resolvePrSourceContext(pull = {}) { const body = String(pull?.body || ''); const block = parseWorkflowSourceBlock(body); const issueNumber = extractIssueNumberFromPull(pull); + const noAutomation = hasNoAutomationWorkflowContext(pull); const markerType = normalizeSourceType(parseHtmlMarker(body, 'workflow-source')); const blockType = normalizeSourceType(block.origin || block.source || block.type); @@ -305,12 +365,13 @@ function resolvePrSourceContext(pull = {}) { labelType !== SOURCE_TYPES.UNKNOWN ), requiresIssue: sourceType === SOURCE_TYPES.GITHUB_ISSUE, + noAutomation, }; } function hasValidNonIssueSourceContext(pull = {}) { const context = resolvePrSourceContext(pull); - return context.isValid && !context.requiresIssue; + return context.isValid && !context.requiresIssue && !context.noAutomation; } function formatSourceContextForLog(context = {}) { @@ -324,6 +385,9 @@ function formatSourceContextForLog(context = {}) { if (context.automation) { parts.push(`automation=${context.automation}`); } + if (context.noAutomation) { + parts.push('no_automation=true'); + } return parts.join(' '); } @@ -335,6 +399,7 @@ module.exports = { parseWorkflowSourceBlock, sourceTypeFromCheckedTemplate, sourceTypeFromLabels, + hasNoAutomationWorkflowContext, resolvePrSourceContext, hasValidNonIssueSourceContext, formatSourceContextForLog, diff --git a/scripts/aggregate_agent_metrics.py b/scripts/aggregate_agent_metrics.py index 7ebe4ae89..e4acfeeac 100755 --- a/scripts/aggregate_agent_metrics.py +++ b/scripts/aggregate_agent_metrics.py @@ -1088,13 +1088,21 @@ def _artifact_selection_contract(selection: dict[str, Any], selection_path: Path for item in statuses if item["status"] == "missing" or item["selected_count"] <= 0 ] + missing_priority_families = selection.get("missing_priority_families") + if isinstance(missing_priority_families, (list, tuple)): + missing_priority_families = [ + str(family) for family in missing_priority_families if isinstance(family, str) + ] + else: + missing_priority_families = [] + return { "schema": selection.get("schema") or "unknown", "path": selection_path.as_posix(), "status": selection.get("status") or "unknown", "selected_count": _safe_int(selection.get("selected_count")) or 0, "candidate_count": _safe_int(selection.get("candidate_count")) or 0, - "missing_priority_families": list(selection.get("missing_priority_families") or []), + "missing_priority_families": missing_priority_families, "terminal_artifact_families": statuses, "missing_terminal_artifact_families": missing_terminal, }