Files
blackroad-os-web/.github/workflows/autonomous-issue-manager.yml
Your Name 64c51ba295 fix: Pin all GitHub Actions to full commit SHAs (13 files)
Security compliance - SHA pinning for all actions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-14 23:01:02 -06:00

400 lines
16 KiB
YAML

# .github/workflows/autonomous-issue-manager.yml
# Autonomous issue creation, triage, and management
name: "Autonomous Issue Manager"
on:
issues:
types: [opened, edited, labeled, assigned]
issue_comment:
types: [created]
schedule:
- cron: '0 9 * * *' # Daily at 9 AM - stale check
workflow_run:
workflows: ["Autonomous Orchestrator", "Autonomous Self-Healer"]
types: [completed]
conclusions: [failure]
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: true
type: choice
options:
- triage_all
- cleanup_stale
- generate_report
- create_health_issues
permissions:
contents: read
issues: write
pull-requests: write
env:
BLACKROAD_AGENT_API: https://blackroad-agents.amundsonalexa.workers.dev
STALE_DAYS: 30
CLOSE_DAYS: 7
jobs:
# ============================================
# Smart Issue Triage
# ============================================
triage:
name: "Smart Triage"
if: github.event_name == 'issues' && github.event.action == 'opened'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: AI Analysis
id: ai
run: |
TITLE="${{ github.event.issue.title }}"
BODY="${{ github.event.issue.body }}"
# Call AI for smart categorization
ANALYSIS=$(curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/analyze-issue" \
-H "Content-Type: application/json" \
-d '{
"title": "'"$TITLE"'",
"body": "'"$(echo "$BODY" | head -c 2000 | jq -Rs .)"'",
"repo": "${{ github.repository }}"
}' 2>/dev/null || echo '{}')
echo "analysis=$ANALYSIS" >> $GITHUB_OUTPUT
# Parse AI response for labels
LABELS=$(echo "$ANALYSIS" | jq -r '.labels // [] | join(",")' 2>/dev/null || echo "")
PRIORITY=$(echo "$ANALYSIS" | jq -r '.priority // "normal"' 2>/dev/null || echo "normal")
ASSIGNEE=$(echo "$ANALYSIS" | jq -r '.assignee // ""' 2>/dev/null || echo "")
echo "labels=$LABELS" >> $GITHUB_OUTPUT
echo "priority=$PRIORITY" >> $GITHUB_OUTPUT
echo "assignee=$ASSIGNEE" >> $GITHUB_OUTPUT
- name: Keyword-Based Labeling
id: keywords
run: |
TITLE="${{ github.event.issue.title }}"
BODY="${{ github.event.issue.body }}"
TEXT="$TITLE $BODY"
LABELS=""
# Type detection
echo "$TEXT" | grep -qi "bug\|error\|broken\|not working\|crash\|fail" && LABELS="$LABELS,bug"
echo "$TEXT" | grep -qi "feature\|add\|new\|enhance\|request" && LABELS="$LABELS,enhancement"
echo "$TEXT" | grep -qi "question\|how\|help\|what\|why" && LABELS="$LABELS,question"
echo "$TEXT" | grep -qi "doc\|documentation\|readme\|typo" && LABELS="$LABELS,documentation"
# Area detection
echo "$TEXT" | grep -qi "security\|vulnerability\|cve\|auth" && LABELS="$LABELS,security"
echo "$TEXT" | grep -qi "performance\|slow\|memory\|cpu" && LABELS="$LABELS,performance"
echo "$TEXT" | grep -qi "ui\|frontend\|css\|style\|design" && LABELS="$LABELS,frontend"
echo "$TEXT" | grep -qi "api\|backend\|server\|database" && LABELS="$LABELS,backend"
echo "$TEXT" | grep -qi "ci\|deploy\|workflow\|action" && LABELS="$LABELS,infrastructure"
# Priority detection
echo "$TEXT" | grep -qi "urgent\|critical\|asap\|important\|blocker" && LABELS="$LABELS,priority:high"
echo "$TEXT" | grep -qi "minor\|low\|when possible" && LABELS="$LABELS,priority:low"
# Clean up labels
LABELS=$(echo "$LABELS" | sed 's/^,//' | sed 's/,,/,/g')
echo "labels=$LABELS" >> $GITHUB_OUTPUT
- name: Apply Labels
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
script: |
const aiLabels = '${{ steps.ai.outputs.labels }}'.split(',').filter(l => l);
const keywordLabels = '${{ steps.keywords.outputs.labels }}'.split(',').filter(l => l);
// Merge and dedupe labels
const allLabels = [...new Set([...aiLabels, ...keywordLabels])].filter(l => l);
if (allLabels.length > 0) {
// Ensure labels exist (create if not)
for (const label of allLabels) {
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label
});
} catch (e) {
// Label doesn't exist, create it
const colors = {
'bug': 'd73a4a',
'enhancement': 'a2eeef',
'question': 'd876e3',
'documentation': '0075ca',
'security': 'b60205',
'performance': 'fbca04',
'frontend': '7057ff',
'backend': '008672',
'infrastructure': 'c5def5',
'priority:high': 'b60205',
'priority:low': 'c2e0c6'
};
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
color: colors[label] || '333333'
}).catch(() => {});
}
}
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
labels: allLabels
});
}
- name: Welcome Response
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
script: |
const labels = '${{ steps.keywords.outputs.labels }}'.split(',').filter(l => l);
const priority = '${{ steps.ai.outputs.priority }}';
let response = `Thanks for opening this issue! 👋\n\n`;
// Add context based on type
if (labels.includes('bug')) {
response += `This has been identified as a **bug report**. `;
response += `To help us investigate:\n`;
response += `- What version are you using?\n`;
response += `- Can you provide steps to reproduce?\n`;
response += `- Any error messages or logs?\n\n`;
} else if (labels.includes('enhancement')) {
response += `This has been identified as a **feature request**. `;
response += `We'll review and prioritize accordingly.\n\n`;
} else if (labels.includes('question')) {
response += `This has been identified as a **question**. `;
response += `Check our [documentation](https://docs.blackroad.io) while you wait for a response.\n\n`;
}
if (priority === 'high') {
response += `⚠️ **High priority** - This will be reviewed soon.\n\n`;
}
response += `**Automated Labels Applied:** ${labels.length > 0 ? labels.map(l => '`' + l + '`').join(', ') : 'None'}\n\n`;
response += `---\n*Triaged by BlackRoad Autonomous Agent*`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: response
});
# ============================================
# Stale Issue Cleanup
# ============================================
stale-cleanup:
name: "Stale Cleanup"
if: github.event_name == 'schedule' || github.event.inputs.action == 'cleanup_stale'
runs-on: ubuntu-latest
steps:
- name: Find Stale Issues
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
script: |
const staleDays = parseInt('${{ env.STALE_DAYS }}');
const closeDays = parseInt('${{ env.CLOSE_DAYS }}');
const now = new Date();
// Get open issues
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100
});
for (const issue of issues.data) {
// Skip PRs
if (issue.pull_request) continue;
const updatedAt = new Date(issue.updated_at);
const daysSinceUpdate = Math.floor((now - updatedAt) / (1000 * 60 * 60 * 24));
const hasStaleLabel = issue.labels.some(l => l.name === 'stale');
const isProtected = issue.labels.some(l =>
['pinned', 'security', 'priority:high', 'in-progress'].includes(l.name)
);
if (isProtected) continue;
// Already marked stale - check if should close
if (hasStaleLabel && daysSinceUpdate >= closeDays) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
state_reason: 'not_planned'
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `This issue has been automatically closed due to inactivity.\n\nIf this is still relevant, please reopen it with additional context.\n\n---\n*Closed by BlackRoad Autonomous Agent*`
});
console.log(`Closed stale issue #${issue.number}`);
}
// Mark as stale
else if (!hasStaleLabel && daysSinceUpdate >= staleDays) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ['stale']
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `This issue has been automatically marked as **stale** because it has not had recent activity.\n\nIt will be closed in ${closeDays} days if no further activity occurs.\n\n---\n*Marked by BlackRoad Autonomous Agent*`
});
console.log(`Marked issue #${issue.number} as stale`);
}
}
# ============================================
# Auto-Create Issues from Failures
# ============================================
failure-issue:
name: "Create Failure Issue"
if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure'
runs-on: ubuntu-latest
steps:
- name: Check for Existing Issue
id: check
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
script: |
// Search for existing issue about this workflow
const workflowName = '${{ github.event.workflow_run.name }}';
const searchQuery = `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open "[Automated] ${workflowName}" in:title`;
const results = await github.rest.search.issuesAndPullRequests({
q: searchQuery
});
core.setOutput('exists', results.data.total_count > 0);
if (results.data.total_count > 0) {
core.setOutput('issue_number', results.data.items[0].number);
}
- name: Create or Update Issue
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
script: |
const workflowName = '${{ github.event.workflow_run.name }}';
const runId = '${{ github.event.workflow_run.id }}';
const runUrl = '${{ github.event.workflow_run.html_url }}';
const exists = '${{ steps.check.outputs.exists }}' === 'true';
const existingNumber = '${{ steps.check.outputs.issue_number }}';
const body = `## Workflow Failure Detected
**Workflow:** ${workflowName}
**Run ID:** ${runId}
**Run URL:** ${runUrl}
**Time:** ${new Date().toISOString()}
### Details
The autonomous orchestrator detected a failure in the ${workflowName} workflow.
### Suggested Actions
1. Review the [workflow run logs](${runUrl})
2. Check recent commits for potential causes
3. Run the self-healer workflow if appropriate
---
*Created by BlackRoad Autonomous Agent*`;
if (exists) {
// Add comment to existing issue
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(existingNumber),
body: `### New Failure Detected\n\n**Run:** ${runUrl}\n**Time:** ${new Date().toISOString()}`
});
} else {
// Create new issue
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `[Automated] ${workflowName} Workflow Failure`,
body: body,
labels: ['bug', 'automated', 'ci-failure']
});
}
# ============================================
# Generate Report
# ============================================
report:
name: "Generate Issue Report"
if: github.event.inputs.action == 'generate_report'
runs-on: ubuntu-latest
steps:
- name: Generate Statistics
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
script: |
// Get all issues
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all',
per_page: 100
});
const stats = {
total: issues.data.length,
open: issues.data.filter(i => i.state === 'open' && !i.pull_request).length,
closed: issues.data.filter(i => i.state === 'closed' && !i.pull_request).length,
bugs: issues.data.filter(i => i.labels.some(l => l.name === 'bug')).length,
enhancements: issues.data.filter(i => i.labels.some(l => l.name === 'enhancement')).length,
stale: issues.data.filter(i => i.labels.some(l => l.name === 'stale')).length
};
console.log('Issue Statistics:', stats);
// Create summary issue
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `[Report] Issue Statistics - ${new Date().toISOString().split('T')[0]}`,
body: `## Issue Statistics Report
| Metric | Count |
|--------|-------|
| Total Issues | ${stats.total} |
| Open | ${stats.open} |
| Closed | ${stats.closed} |
| Bugs | ${stats.bugs} |
| Enhancements | ${stats.enhancements} |
| Stale | ${stats.stale} |
---
*Generated by BlackRoad Autonomous Agent*`,
labels: ['report', 'automated']
});