name: Skill Validator — PR Comment # Posts results from the "Skill Validator — PR Gate" workflow. # Runs with write permissions but never checks out PR code, # so it is safe for fork PRs. on: workflow_run: workflows: ["Skill Validator — PR Gate"] types: [completed] permissions: pull-requests: write actions: read # needed to download artifacts jobs: comment: runs-on: ubuntu-latest if: github.event.workflow_run.event == 'pull_request' steps: - name: Download results artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: skill-validator-results run-id: ${{ github.event.workflow_run.id }} github-token: ${{ github.token }} - name: Post PR comment with results uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const fs = require('fs'); const total = parseInt(fs.readFileSync('total.txt', 'utf8').trim(), 10); if (total === 0) { console.log('No skills/agents were checked — skipping comment.'); return; } const prNumber = parseInt(fs.readFileSync('pr-number.txt', 'utf8').trim(), 10); const exitCode = fs.readFileSync('exit-code.txt', 'utf8').trim(); const skillCount = parseInt(fs.readFileSync('skill-count.txt', 'utf8').trim(), 10); const agentCount = parseInt(fs.readFileSync('agent-count.txt', 'utf8').trim(), 10); const totalChecked = skillCount + agentCount; const marker = ''; const rawOutput = fs.existsSync('sv-output.txt') ? fs.readFileSync('sv-output.txt', 'utf8') : ''; const output = rawOutput.replace(/\x1b\[[0-9;]*m/g, '').trim(); const errorCount = (output.match(/❌/g) || []).length; const warningCount = (output.match(/⚠/g) || []).length; const advisoryCount = (output.match(/ℹ/g) || []).length; let verdict = '✅ All checks passed'; if (exitCode !== '0' || errorCount > 0) { verdict = '⛔ Findings need attention'; } else if (warningCount > 0 || advisoryCount > 0) { verdict = '⚠️ Warnings or advisories found'; } const highlightedLines = output .split('\n') .map(line => line.trim()) .filter(Boolean) .filter(line => !line.startsWith('###')) .filter(line => /^[❌⚠ℹ]/.test(line)); const summaryLines = highlightedLines.length > 0 ? highlightedLines.slice(0, 10) : output .split('\n') .map(line => line.trim()) .filter(Boolean) .filter(line => !line.startsWith('###')) .slice(0, 10); const scopeTable = [ '| Scope | Checked |', '|---|---:|', `| Skills | ${skillCount} |`, `| Agents | ${agentCount} |`, `| Total | ${totalChecked} |`, ]; const severityTable = [ '| Severity | Count |', '|---|---:|', `| ❌ Errors | ${errorCount} |`, `| ⚠️ Warnings | ${warningCount} |`, `| ℹ️ Advisories | ${advisoryCount} |`, ]; const findingsTable = summaryLines.length === 0 ? ['_No findings were emitted by the validator._'] : [ '| Level | Finding |', '|---|---|', ...summaryLines.map(line => { const level = line.startsWith('❌') ? '❌' : line.startsWith('⚠') ? '⚠️' : line.startsWith('ℹ') ? 'ℹ️' : (exitCode !== '0' ? '⛔' : 'ℹ️'); const text = line.replace(/^[❌⚠ℹ️\s]+/, '').replace(/\|/g, '\\|'); return `| ${level} | ${text} |`; }), ]; const body = [ marker, '## 🔍 Skill Validator Results', '', `**${verdict}**`, '', ...scopeTable, '', ...severityTable, '', '### Summary', '', ...findingsTable, '', '
', 'Full validator output', '', '```text', output || 'No validator output captured.', '```', '', '
', '', exitCode !== '0' ? '> **Note:** The validator returned a non-zero exit code. Please review the findings above before merge.' : '', ].filter(Boolean).join('\n'); // Find existing comment with our marker const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: prNumber, per_page: 100, }); const existing = comments.find(c => c.body.includes(marker)); if (existing) { await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, body, }); console.log(`Updated existing comment ${existing.id}`); } else { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: prNumber, body, }); console.log('Created new PR comment'); }