chore: sync workflow templates - #131
Conversation
Automated sync from stranske/Workflows Template hash: 2f7d9a176913 Changes synced from sync-manifest.yml
|
Status | ✅ no new diagnostics |
|
Autofix updated these files:
|
🤖 Keepalive Loop StatusPR #131 | Agent: Codex | Iteration 0/5 Current State
🔍 Failure Classification| Error type | infrastructure | |
There was a problem hiding this comment.
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:optimizetoagents:formattedfor 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 |
There was a problem hiding this comment.
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.
| /^\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 |
| /^['"].*['"]$/, // String literals | ||
| /^\s*$/, // Empty lines | ||
| /^[A-Z_]+\s*=/, // Constants | ||
| /^\|\s*\w+/, // Table rows |
There was a problem hiding this comment.
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).
| /^\|\s*\w+/, // Table rows | |
| /^\|\s*[^|]+\|/, // Table rows (markdown-style with at least two pipes) |
| 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); |
There was a problem hiding this comment.
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.
| const key = concern.toLowerCase().replace(/[^a-z0-9]/g, '').substring(0, 50); | |
| const key = concern.toLowerCase().replace(/[^a-z0-9]/g, ''); |
| 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 |
There was a problem hiding this comment.
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.
| 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); |
| '### \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'; |
There was a problem hiding this comment.
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.
| '### \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'; |
| let structuralAnalysis = ''; | ||
| if (structuralProblems.length > 0) { | ||
| structuralAnalysis = | ||
| '### \u26a0\ufe0f Issues Detected in Original Issue Structure\\n\\n'; |
There was a problem hiding this comment.
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.
| '### \u26a0\ufe0f Issues Detected in Original Issue Structure\\n\\n'; | |
| '### ⚠️ Issues Detected in Original Issue Structure\\n\\n'; |
|
|
||
| // Consolidated concerns as tasks | ||
| const concernTasks = [...allConcerns.values()] | ||
| .sort((a, b) => (b.sources.length - a.sources.length)) |
There was a problem hiding this comment.
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.
| .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); | |
| }) |
| .sort((a, b) => (b.sources.length - a.sources.length)) | ||
| .map(c => { | ||
| const sourceTag = c.sources.length > 1 | ||
| ? ' *(agreed by both providers)*' : ''; |
There was a problem hiding this comment.
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.
| ? ' *(agreed by both providers)*' : ''; | |
| ? ' *(agreed by ' + c.sources.length + ' providers)*' : ''; |
| - 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'); | ||
|
|
There was a problem hiding this comment.
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).
| /^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 |
There was a problem hiding this comment.
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).
| /^\w+\s*=\s*.+$/, // Assignments | |
| /^[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*.*[()\[\].,'"{}\d].*$/, // Code-like assignments |
Sync Summary
Files Updated
Files Skipped
Review Checklist
Source: stranske/Workflows
Manifest:
.github/sync-manifest.yml