Skip to content

chore: sync workflow templates - #130

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

chore: sync workflow templates#130
stranske wants to merge 2 commits into
mainfrom
sync/workflows-28e1781f3ab5

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: 28e1781f3ab5

Changes synced from sync-manifest.yml
Copilot AI review requested due to automatic review settings January 8, 2026 13:44
@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 #130. 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

🤖 Keepalive Loop Status

PR #130 | 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 commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

Status | ✅ no new diagnostics
History points | 0
Timestamp | 2026-01-08 13:46:21 UTC
Report artifact | autofix-report-pr-130
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 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 central Workflows repository, introducing a significantly enhanced version of the agents-verify-to-issue.yml workflow that extracts comprehensive data from PR verification feedback to create well-structured follow-up issues.

Key changes:

  • Enhanced data extraction including provider comparison reports, keepalive state analysis, and structural problem detection
  • Intelligent filtering of non-actionable tasks (code snippets, type hints) from actual work items
  • Multi-provider concern consolidation with deduplication logic
  • Structured issue generation with agents:formatted label for immediate agent readiness

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 priority assignment logic sets priority to 'high' when multiple providers report the same concern, but the 'normal' priority initially assigned based on position never gets used in the output. This priority field is calculated but never referenced in the issue body generation, making it dead code.

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.
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) {

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 task length filters (task.length > 10 and task.length < 150 on line 168, then task.length > 10 and task.length < 200 on line 257) use different thresholds without explanation. This inconsistency could lead to confusion. Consider using consistent thresholds or documenting why different limits are needed for different contexts.

Suggested change
task.length > 10 && task.length < 200) {
task.length > 10 && task.length < 150) {

Copilot uses AI. Check for mistakes.
const issueBody = sections.join('\n');

// Create the issue with agents:formatted label so it's ready for agent work
const { data: issue } = await github.rest.issues.create({

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 variable declaration uses object destructuring with const { data: issue } but the result is used as issue.data.number on line 528. This will fail because issue refers to the data property itself, not the parent object. Either remove the destructuring and use const issue = await ... then access issue.data.number, or keep the destructuring and use issue.number on line 528.

Copilot uses AI. Check for mistakes.
labels: ['follow-up', 'agents:formatted']
});

core.info('Created issue #' + issue.data.number);

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.

Following up from the previous issue, this line references issue.data.number but issue was destructured from the response as data: issue, so it should be issue.number instead.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +84
/#### (\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+)%';

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 for extracting provider details is fragile. The pattern expects exact whitespace formatting (\n- \*\*) which will break if GitHub's comment rendering or any formatter changes whitespace. Consider using more flexible patterns like \s*-\s*\*\* that allow for variations in whitespace.

Suggested change
/#### (\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+)%';
/^####\s+(\w+(?:-\w+)?)/gm
)];
// 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, allowing flexible whitespace
const detailPattern = '####\\s+' + providerName +
'\\s*-\\s*\\*\\*Model:\\*\\*\\s*([^\\n]+)' +
'\\s*-\\s*\\*\\*Verdict:\\*\\*\\s*(\\w+)' +
'\\s*-\\s*\\*\\*Confidence:\\*\\*\\s*(\\d+)%';

Copilot uses AI. Check for mistakes.
Comment on lines +252 to +257
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) {

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 patterns on lines 256 and 252 for filtering unchecked tasks are inconsistent. Line 252 uses ^- \[ \] (with spaces inside brackets) while line 256 uses different patterns. Additionally, the pattern on line 256 checking for ^\w+:\s*\w+\s*$ may be too broad and could filter out legitimate tasks like "Update: fix the config". Consider consolidating these patterns into the same isNonActionable function used earlier for consistency.

Suggested change
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) {
const taskMatches = [...prBody.matchAll(/^- \[\s*\]\s+(.+)$/gm)];
const isNonActionableTask = (task) => {
// Filter out code snippets, signatures, comments, and empty lines
if (task.match(/^```|^class |^def |^#|^\s*$/)) {
return true;
}
// Ignore tasks that are implausibly short or long
if (task.length <= 10 || task.length >= 200) {
return true;
}
return false;
};
for (const m of taskMatches) {
const task = m[1].trim();
if (!isNonActionableTask(task)) {

Copilot uses AI. Check for mistakes.
Comment on lines +265 to +269
let linkedIssueNumber = null;
const issueMatch = prBody.match(/Issue #(\d+)|meta:issue:(\d+)/);
if (issueMatch) {
linkedIssueNumber = issueMatch[1] || issueMatch[2];
}

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 code extracts a linked issue number from the PR body but never uses it except to optionally display it in the output. If the linked issue reference is important for tracking the relationship between issues, consider linking the newly created issue back to the original one using GitHub's issue references or adding it to a project board.

Copilot uses AI. Check for mistakes.
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) {

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 magic number 20 for task count threshold lacks context. Consider extracting this to a constant at the beginning of the script with a descriptive name like MAX_REASONABLE_TASK_COUNT or documenting why 20 is the appropriate threshold for detecting structural problems.

Copilot uses AI. Check for mistakes.
Comment on lines +219 to +352
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';
}

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 hard-coded limits like slice(0, 3) and slice(0, 5) should be extracted as constants. These magic numbers control how many examples are shown in the issue but are scattered throughout the code, making it difficult to adjust the verbosity level consistently.

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 of a normalized string as the key. This could lead to false positives where different concerns with similar beginnings are incorrectly merged. Consider using the full normalized string or a hash of it for more accurate 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.
@stranske stranske closed this Jan 8, 2026
@stranske
stranske deleted the sync/workflows-28e1781f3ab5 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