Skip to content

chore: sync workflow templates - #131

Closed
stranske wants to merge 2 commits into
mainfrom
sync/workflows-2f7d9a176913
Closed

chore: sync workflow templates#131
stranske wants to merge 2 commits into
mainfrom
sync/workflows-2f7d9a176913

Conversation

@stranske

@stranske stranske commented Jan 8, 2026

Copy link
Copy Markdown
Owner

Sync Summary

Files Updated

  • agents-verify-to-issue.yml: Verify to issue - creates follow-up issues from verification feedback (Phase 4E)
  • issue_formatter.py: Issue formatter - converts raw text to AGENT_ISSUE_TEMPLATE format

Files Skipped

  • pr-00-gate.yml: File exists and sync_mode is create_only
  • ci.yml: File exists and sync_mode is create_only
  • dependabot.yml: File exists and sync_mode is create_only

Review Checklist

  • CI passes with updated workflows
  • No repo-specific customizations were overwritten

Source: stranske/Workflows
Manifest: .github/sync-manifest.yml

Automated sync from stranske/Workflows
Template hash: 2f7d9a176913

Changes synced from sync-manifest.yml
Copilot AI review requested due to automatic review settings January 8, 2026 13:55
@stranske stranske added sync Automated sync from Workflows automated Automated sync from Workflows labels Jan 8, 2026
@github-actions

github-actions Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

⚠️ Action Required: Unable to determine source issue for PR #131. The PR title, branch name, or body must contain the issue number (e.g. #123, branch: issue-123, or the hidden marker ).

@github-actions github-actions Bot added the autofix Let bots format/lint automatically label Jan 8, 2026
@github-actions github-actions Bot added the autofix:patch Autofix patch available label Jan 8, 2026
@github-actions

github-actions Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

Status | ✅ no new diagnostics
History points | 0
Timestamp | 2026-01-08 13:57:24 UTC
Report artifact | autofix-report-pr-131
Remaining | ∅
New | ∅
No additional artifacts

@github-actions

github-actions Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

Autofix updated these files:

  • scripts/langchain/issue_formatter.py

@github-actions

github-actions Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

🤖 Keepalive Loop Status

PR #131 | Agent: Codex | Iteration 0/5

Current State

Metric Value
Iteration progress [----------] 0/5
Action wait (missing-agent-label)
Disposition skipped (transient)
Gate success
Tasks 0/5 complete
Keepalive ❌ disabled
Autofix ❌ disabled

🔍 Failure Classification

| Error type | infrastructure |
| Error category | resource |
| Suggested recovery | Confirm the referenced resource exists (repo, PR, branch, workflow, or file). |

@github-actions github-actions Bot removed the autofix:patch Autofix patch available label Jan 8, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR syncs workflow templates from the stranske/Workflows repository, significantly expanding the agents-verify-to-issue.yml workflow to extract and structure more comprehensive data from merged PRs when creating follow-up issues.

Key changes:

  • Enhanced data extraction from provider comparison reports, keepalive states, and PR bodies
  • Added structural problem detection to identify issues that may have caused unproductive agent work
  • Improved issue body formatting with categorized sections for concerns, agent history, and tasks
  • Changed default label from agents:optimize to agents:formatted for created issues

/^```\w*$/, // Code fence markers
/^class\s+\w+/, // Class definitions
/^def\s+\w+/, // Function definitions
/^\w+:\s*(str|int|float|bool|Any|list|dict|None)/i, // Type annotations

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern /^\w+:\s*(str|int|float|bool|Any|list|dict|None)/i is case-insensitive (flag i), but several of the type names (Any, None) are case-sensitive in Python. This could miss patterns like field: ANY or field: none while incorrectly matching non-type annotations. Consider either removing the case-insensitive flag or explicitly handling common case variations.

Suggested change
/^\w+:\s*(str|int|float|bool|Any|list|dict|None)/i, // Type annotations
/^\w+:\s*(str|int|float|bool|Any|list|dict|None)/, // Type annotations

Copilot uses AI. Check for mistakes.
/^['"].*['"]$/, // String literals
/^\s*$/, // Empty lines
/^[A-Z_]+\s*=/, // Constants
/^\|\s*\w+/, // Table rows

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern /^\|\s*\w+/ for table rows will match lines like | variable but also incorrectly match lines starting with pipe characters followed by any word, which could lead to false positives. Consider making this more specific to table syntax (e.g., checking for multiple pipe characters or specific table patterns).

Suggested change
/^\|\s*\w+/, // Table rows
/^\|\s*[^|]+\|/, // Table rows (markdown-style with at least two pipes)

Copilot uses AI. Check for mistakes.
for (const provider of providerData.providers) {
for (const concern of provider.concerns) {
// Normalize for deduplication
const key = concern.toLowerCase().replace(/[^a-z0-9]/g, '').substring(0, 50);

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concern deduplication logic uses only the first 50 characters for the key (line 280), which could cause different concerns to be incorrectly merged if they share the same initial text. For example, "Missing error handling in function X" and "Missing error handling in function Y" would be treated as duplicates. Consider using a longer substring or the full text for deduplication.

Suggested change
const key = concern.toLowerCase().replace(/[^a-z0-9]/g, '').substring(0, 50);
const key = concern.toLowerCase().replace(/[^a-z0-9]/g, '');

Copilot uses AI. Check for mistakes.
Comment on lines +284 to +289
sources: [provider.name],
priority: provider.concerns.indexOf(concern) === 0 ? 'high' : 'normal'
});
} else {
allConcerns.get(key).sources.push(provider.name);
allConcerns.get(key).priority = 'high'; // Multiple providers = high priority

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition provider.concerns.indexOf(concern) === 0 only sets priority to 'high' for the first concern in each provider's list, but the priority is never actually used later in the code. Either remove this unused priority tracking or implement logic that uses it to sort or highlight high-priority concerns.

Suggested change
sources: [provider.name],
priority: provider.concerns.indexOf(concern) === 0 ? 'high' : 'normal'
});
} else {
allConcerns.get(key).sources.push(provider.name);
allConcerns.get(key).priority = 'high'; // Multiple providers = high priority
sources: [provider.name]
});
} else {
allConcerns.get(key).sources.push(provider.name);

Copilot uses AI. Check for mistakes.
Comment on lines +376 to +383
'### \u26a0\ufe0f Issues Detected in Original Issue Structure\\n\\n';
for (const prob of structuralProblems) {
structuralAnalysis += '**Problem:** ' + prob.problem + '\\n';
structuralAnalysis += '- **Cause:** ' + prob.cause + '\\n';
structuralAnalysis += '- **Fix:** ' + prob.fix + '\\n';
if (prob.examples && prob.examples.length > 0) {
structuralAnalysis +=
'- **Examples:** `' + prob.examples.join('`, `') + '`\\n';

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structural analysis string contains literal \n escape sequences (lines 376, 378-383) instead of actual newlines, which will render as the text "\n" in the output rather than creating line breaks. Use actual newlines or ensure these are properly handled when the string is used.

Suggested change
'### \u26a0\ufe0f Issues Detected in Original Issue Structure\\n\\n';
for (const prob of structuralProblems) {
structuralAnalysis += '**Problem:** ' + prob.problem + '\\n';
structuralAnalysis += '- **Cause:** ' + prob.cause + '\\n';
structuralAnalysis += '- **Fix:** ' + prob.fix + '\\n';
if (prob.examples && prob.examples.length > 0) {
structuralAnalysis +=
'- **Examples:** `' + prob.examples.join('`, `') + '`\\n';
'### \u26a0\ufe0f Issues Detected in Original Issue Structure\n\n';
for (const prob of structuralProblems) {
structuralAnalysis += '**Problem:** ' + prob.problem + '\n';
structuralAnalysis += '- **Cause:** ' + prob.cause + '\n';
structuralAnalysis += '- **Fix:** ' + prob.fix + '\n';
if (prob.examples && prob.examples.length > 0) {
structuralAnalysis +=
'- **Examples:** `' + prob.examples.join('`, `') + '`\n';

Copilot uses AI. Check for mistakes.
let structuralAnalysis = '';
if (structuralProblems.length > 0) {
structuralAnalysis =
'### \u26a0\ufe0f Issues Detected in Original Issue Structure\\n\\n';

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Unicode escape sequence \u26a0\ufe0f (line 376) for the warning emoji may not render correctly in all contexts. Consider using the actual emoji character or testing that this renders as expected in GitHub issue bodies.

Suggested change
'### \u26a0\ufe0f Issues Detected in Original Issue Structure\\n\\n';
'### ⚠️ Issues Detected in Original Issue Structure\\n\\n';

Copilot uses AI. Check for mistakes.

// Consolidated concerns as tasks
const concernTasks = [...allConcerns.values()]
.sort((a, b) => (b.sources.length - a.sources.length))

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sorting of concerns by source count (line 312) could result in inconsistent ordering when concerns have the same number of sources. Consider adding a secondary sort key (e.g., alphabetically by concern text) to ensure deterministic ordering.

Suggested change
.sort((a, b) => (b.sources.length - a.sources.length))
.sort((a, b) => {
const diff = b.sources.length - a.sources.length;
return diff !== 0 ? diff : a.text.localeCompare(b.text);
})

Copilot uses AI. Check for mistakes.
.sort((a, b) => (b.sources.length - a.sources.length))
.map(c => {
const sourceTag = c.sources.length > 1
? ' *(agreed by both providers)*' : '';

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition c.sources.length > 1 checks for more than one source, but the message says "agreed by both providers" which assumes exactly two providers. If there are more than two providers, this message would be inaccurate. Consider using more generic language like "agreed by multiple providers" or showing the actual count/list of agreeing providers.

Suggested change
? ' *(agreed by both providers)*' : '';
? ' *(agreed by ' + c.sources.length + ' providers)*' : '';

Copilot uses AI. Check for mistakes.
Comment on lines +38 to 400
- name: Extract all available data
id: extract
if: steps.check-merged.outputs.merged == 'true'
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const prNumber = context.payload.pull_request.number;
const prBody = context.payload.pull_request.body || '';

// Fetch all comments
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
issue_number: prNumber,
per_page: 100
});

// Look for verification report comment
const verifyComment = comments.find(c =>
c.body.includes('## PR Verification Report') ||
c.body.includes('## PR Verification Comparison') ||
c.body.includes('### Concerns') ||
c.body.includes('Verdict:')
const allComments = comments.map(c => c.body).join('\n---COMMENT_SEPARATOR---\n');

// ========================================
// 1. EXTRACT PROVIDER COMPARISON DATA
// ========================================
const providerData = { providers: [], agreement: null, uniqueInsights: [] };

// Look for Provider Comparison Report
const comparisonComment = comments.find(c =>
c.body.includes('## Provider Comparison Report') ||
c.body.includes('Provider Summary')
);

if (!verifyComment) {
const msg = 'No verification comment found on this PR. ';
core.setFailed(msg + 'Add verify:evaluate or verify:compare label first.');
return;
if (comparisonComment) {
const body = comparisonComment.body;

// Extract individual provider details from expandable section
const providerMatches = [...body.matchAll(
/#### (\w+(?:-\w+)?)\n/g
)];

// For each provider header found, extract their details
for (const headerMatch of providerMatches) {
const providerName = headerMatch[1];
// Build pattern to extract this provider's section
const detailPattern = '#### ' + providerName +
'\\n- \\*\\*Model:\\*\\* ([^\\n]+)' +
'\\n- \\*\\*Verdict:\\*\\* (\\w+)' +
'\\n- \\*\\*Confidence:\\*\\* (\\d+)%';
const detailMatch = body.match(new RegExp(detailPattern));
if (!detailMatch) continue;

const provider = {
name: providerName,
model: detailMatch[1],
verdict: detailMatch[2],
confidence: parseInt(detailMatch[3]),
concerns: []
};

// Extract concerns for this provider
const concernsPattern = '#### ' + providerName +
'[\\s\\S]*?- \\*\\*Concerns:\\*\\*' +
'([\\s\\S]*?)(?=####|### |$)';
const providerSection = body.match(new RegExp(concernsPattern));
if (providerSection) {
const concernRegex = /^\s*-\s+(.+)$/gm;
const concernLines = providerSection[1].match(concernRegex) || [];
provider.concerns = concernLines.map(
l => l.replace(/^\s*-\s+/, '').trim()
);
}

// Extract scores
const scoresPattern = '#### ' + providerName +
'[\\s\\S]*?- \\*\\*Scores:\\*\\*' +
'([\\s\\S]*?)(?=- \\*\\*Summary|####|$)';
const scoresMatch = body.match(new RegExp(scoresPattern));
if (scoresMatch) {
provider.scores = {};
const scoreLines = scoresMatch[1].match(/(\w+):\s*([\d.]+)\/10/g) || [];
for (const s of scoreLines) {
const [, name, val] = s.match(/(\w+):\s*([\d.]+)/) || [];
if (name) provider.scores[name] = parseFloat(val);
}
}

providerData.providers.push(provider);
}

// Extract agreement section
const agreeRegex = /### Agreement\n([\s\S]*?)(?=### |$)/;
const agreementMatch = body.match(agreeRegex);
if (agreementMatch) {
providerData.agreement = agreementMatch[1].trim();
}

// Extract unique insights
const insightsMatch = body.match(/### Unique Insights\n([\s\S]*?)(?=###|$)/);
if (insightsMatch) {
providerData.uniqueInsights = insightsMatch[1].trim();
}
}

const comment = verifyComment.body;
core.info('Found verification comment');
// ========================================
// 2. EXTRACT KEEPALIVE STATE & ANALYZE PATTERNS
// ========================================
const keepaliveData = {
iteration: 0,
attemptedTasks: [],
failureReason: '',
uncheckedCount: 0,
totalTasks: 0
};

// Patterns that indicate non-actionable "tasks" (code snippets, type hints, etc.)
const nonActionablePatterns = [
/^```\w*$/, // Code fence markers
/^class\s+\w+/, // Class definitions
/^def\s+\w+/, // Function definitions
/^\w+:\s*(str|int|float|bool|Any|list|dict|None)/i, // Type annotations
/^\w+\s*=\s*.+$/, // Assignments
/^#\s/, // Comments
/^['"].*['"]$/, // String literals
/^\s*$/, // Empty lines
/^[A-Z_]+\s*=/, // Constants
/^\|\s*\w+/, // Table rows
];

// Extract CONCERNS section
const concernsMatch = comment.match(/### Concerns\s*\n([\s\S]*?)(?=###|##|$)/i);
let concerns = concernsMatch ? concernsMatch[1].trim() : '';
const isNonActionable = (task) => {
return nonActionablePatterns.some(p => p.test(task.trim())) ||
task.length < 10 ||
task.length > 150;
};

// Also try alternate formats
if (!concerns) {
const altMatch = comment.match(/\*\*Concerns:\*\*\s*([\s\S]*?)(?=\*\*|##|$)/i);
concerns = altMatch ? altMatch[1].trim() : '';
const keepaliveMatch = allComments.match(/keepalive-state:v1\s*(\{[\s\S]*?\})\s*-->/);
if (keepaliveMatch) {
try {
const state = JSON.parse(keepaliveMatch[1]);
keepaliveData.iteration = state.iteration || 0;
keepaliveData.failureReason = state.last_reason || '';
keepaliveData.uncheckedCount = state.tasks?.unchecked || 0;
keepaliveData.totalTasks = state.tasks?.total || 0;

// Analyze attempted tasks for non-actionable patterns
if (state.attempted_tasks) {
keepaliveData.attemptedTasks = state.attempted_tasks.map(t => ({
task: t.task,
iteration: t.iteration,
isNonActionable: isNonActionable(t.task)
}));
}
} catch (e) {
core.warning('Failed to parse keepalive state: ' + e.message);
}
}

// Extract low scores (anything < 7/10)
const scoreMatches = [...comment.matchAll(/(\w+):\s*(\d+)\/10/gi)];
const lowScores = scoreMatches
.filter(m => parseInt(m[2]) < 7)
.map(m => `- ${m[1]}: ${m[2]}/10`);
// ========================================
// 2b. ANALYZE ISSUE STRUCTURE PROBLEMS
// ========================================
const structuralProblems = [];

// Extract verdict
const verdictMatch = comment.match(/Verdict:\s*\*?\*?(\w+)\*?\*?/i);
const verdict = verdictMatch ? verdictMatch[1] : 'Unknown';
// Check for high task count (often indicates code snippets parsed as tasks)
if (keepaliveData.totalTasks > 20) {
structuralProblems.push({
problem: 'Excessive task count (' + keepaliveData.totalTasks + ' items)',
cause: 'Code snippets or examples in the issue were likely ' +
'parsed as individual tasks',
fix: 'Use fenced code blocks (```) for code examples; ' +
'keep tasks as actionable work items'
});
}

// Build summary
let summary = '';
if (concerns) {
summary += '### Concerns from Verification\n\n' + concerns + '\n\n';
// Check for non-actionable attempted tasks
const nonActionableTasks = keepaliveData.attemptedTasks.filter(t => t.isNonActionable);
if (nonActionableTasks.length > 0) {
structuralProblems.push({
problem: 'Agent attempted ' + nonActionableTasks.length +
' non-actionable items',
cause: 'Type annotations, code snippets, or fragments were ' +
'listed as tasks',
fix: 'Ensure each task checkbox describes a concrete action ' +
'(e.g., "Implement X" not "field: type")',
examples: nonActionableTasks.slice(0, 3).map(t => t.task)
});
}
if (lowScores.length > 0) {
summary += '### Scores Below 7/10\n\n' + lowScores.join('\n') + '\n\n';

// Check for unproductive iterations
if (keepaliveData.failureReason === 'max-iterations-unproductive') {
structuralProblems.push({
problem: 'Agent hit max unproductive iterations',
cause: 'Tasks may have been unclear, blocked, or not ' +
'achievable in isolation',
fix: 'Break complex tasks into smaller, independently ' +
'verifiable steps'
});
}
if (!summary) {
summary = 'No specific concerns extracted from verification report.';
summary += '\n\nPlease review the original verification comment for details.';

// Check for high unchecked ratio
const uncheckedRatio = keepaliveData.totalTasks > 0
? keepaliveData.uncheckedCount / keepaliveData.totalTasks : 0;
if (uncheckedRatio > 0.5) {
structuralProblems.push({
problem: 'Over 50% of tasks remain unchecked after ' +
keepaliveData.iteration + ' iterations',
cause: 'Tasks may be too broad, ambiguous, or have ' +
'hidden dependencies',
fix: 'Prioritize the 3-5 most critical tasks; defer ' +
'others to follow-up issues'
});
}

// Set outputs using environment file (handles multi-line content)
const fs = require('fs');
// ========================================
// 3. EXTRACT UNCHECKED TASKS FROM PR BODY
// ========================================
const uncheckedTasks = [];
const taskMatches = [...prBody.matchAll(/^- \[ \]\s+(.+)$/gm)];
for (const m of taskMatches) {
const task = m[1].trim();
// Filter out code snippets that aren't real tasks
if (!task.match(/^```|^class |^def |^\w+:\s*\w+\s*$|^#|^\s*$/) &&
task.length > 10 && task.length < 200) {
uncheckedTasks.push(task);
}
}

// ========================================
// 4. EXTRACT LINKED ISSUE INFO
// ========================================
let linkedIssueNumber = null;
const issueMatch = prBody.match(/Issue #(\d+)|meta:issue:(\d+)/);
if (issueMatch) {
linkedIssueNumber = issueMatch[1] || issueMatch[2];
}

// ========================================
// 5. BUILD CONSOLIDATED CONCERNS LIST
// ========================================
const allConcerns = new Map(); // Use map to dedupe

// Add concerns from all providers
for (const provider of providerData.providers) {
for (const concern of provider.concerns) {
// Normalize for deduplication
const key = concern.toLowerCase().replace(/[^a-z0-9]/g, '').substring(0, 50);
if (!allConcerns.has(key)) {
allConcerns.set(key, {
text: concern,
sources: [provider.name],
priority: provider.concerns.indexOf(concern) === 0 ? 'high' : 'normal'
});
} else {
allConcerns.get(key).sources.push(provider.name);
allConcerns.get(key).priority = 'high'; // Multiple providers = high priority
}
}
}

// ========================================
// OUTPUT RESULTS
// ========================================
const envFile = process.env.GITHUB_OUTPUT;
const writeMultiline = (name, value) => {
const delim = 'EOF_' + Math.random().toString(36).substring(2);
fs.appendFileSync(envFile, name + '<<' + delim + '\n' + value + '\n' + delim + '\n');
};

// Provider summary
const providerSummary = providerData.providers.map(p =>
'- **' + p.name + '** (' + p.model + '): ' +
p.verdict + ' @ ' + p.confidence + '%'
).join('\n') || 'No provider comparison found.';
writeMultiline('provider_summary', providerSummary);

// Consolidated concerns as tasks
const concernTasks = [...allConcerns.values()]
.sort((a, b) => (b.sources.length - a.sources.length))
.map(c => {
const sourceTag = c.sources.length > 1
? ' *(agreed by both providers)*' : '';
return '- [ ] ' + c.text + sourceTag;
})
.join('\n');
writeMultiline('concern_tasks',
concernTasks || 'No specific concerns identified.');

// Unchecked tasks from PR
const uncheckedTasksStr = uncheckedTasks
.map(t => '- [ ] ' + t).join('\n');
writeMultiline('unchecked_tasks',
uncheckedTasksStr || 'All tasks appear complete.');

// Keepalive analysis with structural problem detection
let keepaliveAnalysis = '';
if (keepaliveData.iteration > 0) {
keepaliveAnalysis = '**Agent ran ' + keepaliveData.iteration +
' iterations**\n';
if (keepaliveData.failureReason) {
keepaliveAnalysis += '- Stop reason: `' +
keepaliveData.failureReason + '`\n';
}
if (keepaliveData.uncheckedCount > 0) {
keepaliveAnalysis += '- Remaining unchecked items: ' +
keepaliveData.uncheckedCount + ' of ' +
keepaliveData.totalTasks + '\n';
}

// Show non-actionable tasks the agent tried
const nonActionable = keepaliveData.attemptedTasks
.filter(t => t.isNonActionable);
if (nonActionable.length > 0) {
keepaliveAnalysis +=
'\n**⚠️ Non-actionable items agent attempted:**\n';
for (const t of nonActionable.slice(0, 5)) {
keepaliveAnalysis +=
'- `' + t.task + '` (iteration ' + t.iteration + ')\n';
}
keepaliveAnalysis +=
'\n*These look like code snippets or type hints.*\n';
}

// Show actionable tasks that weren't completed
const actionable = keepaliveData.attemptedTasks
.filter(t => !t.isNonActionable);
if (actionable.length > 0) {
keepaliveAnalysis +=
'\n**Actionable tasks attempted but not completed:**\n';
for (const t of actionable.slice(-5)) {
keepaliveAnalysis +=
'- Iteration ' + t.iteration + ': ' + t.task + '\n';
}
}
}
writeMultiline('keepalive_analysis',
keepaliveAnalysis || 'No agent history available.');

// Use delimiter for multi-line output
const delim = 'EOF_' + Math.random().toString(36).substring(2);
const outLine = `concerns_summary<<${delim}\n${summary}\n${delim}\n`;
fs.appendFileSync(envFile, outLine);
// Structural problems analysis
let structuralAnalysis = '';
if (structuralProblems.length > 0) {
structuralAnalysis =
'### \u26a0\ufe0f Issues Detected in Original Issue Structure\\n\\n';
for (const prob of structuralProblems) {
structuralAnalysis += '**Problem:** ' + prob.problem + '\\n';
structuralAnalysis += '- **Cause:** ' + prob.cause + '\\n';
structuralAnalysis += '- **Fix:** ' + prob.fix + '\\n';
if (prob.examples && prob.examples.length > 0) {
structuralAnalysis +=
'- **Examples:** `' + prob.examples.join('`, `') + '`\\n';
}
structuralAnalysis += '\n';
}
}
writeMultiline('structural_analysis', structuralAnalysis);

core.setOutput('verdict', verdict);
core.setOutput('has_concerns', (concerns || lowScores.length > 0) ? 'true' : 'false');
// Metadata
core.setOutput('linked_issue', linkedIssueNumber || '');
core.setOutput('provider_count',
String(providerData.providers.length));
core.setOutput('concern_count', String(allConcerns.size));
core.setOutput('unchecked_count', String(uncheckedTasks.length));
core.setOutput('agent_iterations',
String(keepaliveData.iteration));
core.setOutput('has_structural_problems',
structuralProblems.length > 0 ? 'true' : 'false');

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Extract all available data" step (lines 38-400) is extremely long and complex, containing multiple distinct operations: provider data extraction, keepalive analysis, task extraction, concern consolidation, and output formatting. This single step spans over 360 lines of inline JavaScript, making it difficult to maintain, test, and debug. Consider breaking this into multiple smaller steps or extracting the logic into a separate script file (e.g., .github/scripts/extract-verification-data.js).

Copilot uses AI. Check for mistakes.
/^class\s+\w+/, // Class definitions
/^def\s+\w+/, // Function definitions
/^\w+:\s*(str|int|float|bool|Any|list|dict|None)/i, // Type annotations
/^\w+\s*=\s*.+$/, // Assignments

Copilot AI Jan 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex /^\w+\s*=\s*.+$/ for matching assignments (line 157) is overly broad and could match legitimate task descriptions like "status = complete" or "priority = high" that are meant to describe work, not code. Consider making this pattern more specific to code-like assignments (e.g., checking for typical variable naming or language-specific syntax).

Suggested change
/^\w+\s*=\s*.+$/, // Assignments
/^[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*.*[()\[\].,'"{}\d].*$/, // Code-like assignments

Copilot uses AI. Check for mistakes.
@stranske stranske closed this Jan 8, 2026
@stranske
stranske deleted the sync/workflows-2f7d9a176913 branch January 8, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix Let bots format/lint automatically automated Automated sync from Workflows sync Automated sync from Workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants