feat(ci): implement comprehensive GitHub automation workflows
Implemented full GitHub automation system from claude-code-skills-factory with project-specific configuration for claude-skills repository. ## New Workflows - **ci-quality-gate.yml**: Automated linting, testing, and security checks - **claude-code-review.yml**: Enhanced with kill switch and bypass mechanisms - **pr-issue-auto-close.yml**: Auto-close linked issues when PRs merge - **smart-sync.yml**: Bidirectional sync between issues and project board ## Configuration Files - **WORKFLOW_KILLSWITCH**: Emergency workflow disable capability - **branch-protection-config.json**: Branch protection settings - **commit-template.txt**: Standardized commit message template - **AUTOMATION_SETUP.md**: Complete setup and configuration guide ## Templates - **pull_request_template.md**: Enhanced with security and quality checklists ## Key Features ✅ AI-powered code reviews with Claude ✅ Automatic issue closure on PR merge ✅ Bidirectional issue ↔ project board sync ✅ Quality gates (YAML lint, Python syntax, security audit) ✅ Kill switch for emergency workflow disable ✅ Rate limit protection with circuit breakers ✅ 10-second debouncing to prevent sync loops ## Project Configuration - Repository: alirezarezvani/claude-skills - Project Number: 9 - Status: Ready for PROJECTS_TOKEN configuration ## Testing Workflows validated with yamllint and ready for deployment. See .github/AUTOMATION_SETUP.md for complete setup instructions.
This commit is contained in:
207
.github/workflows/pr-issue-auto-close.yml
vendored
Normal file
207
.github/workflows/pr-issue-auto-close.yml
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
---
|
||||
name: Auto-Close Issues on PR Merge
|
||||
|
||||
'on':
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
close-linked-issues:
|
||||
name: Close Issues Linked in PR
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check Workflow Kill Switch
|
||||
run: |
|
||||
if [ -f ".github/WORKFLOW_KILLSWITCH" ]; then
|
||||
STATUS=$(grep "STATUS:" .github/WORKFLOW_KILLSWITCH | awk '{print $2}')
|
||||
if [ "$STATUS" = "DISABLED" ]; then
|
||||
echo "🛑 Workflows disabled by kill switch"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Extract linked issues from PR body
|
||||
id: extract_issues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const prBody = context.payload.pull_request.body || '';
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
// Patterns to detect linked issues
|
||||
// Supports: Fixes #123, Closes #456, Resolves #789, Related to #111, See #222
|
||||
const patterns = [
|
||||
/(?:fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved)\s+#(\d+)/gi,
|
||||
/(?:related\s+to|see|ref|references)\s+#(\d+)/gi
|
||||
];
|
||||
|
||||
const issueNumbers = new Set();
|
||||
|
||||
// Extract issue numbers
|
||||
patterns.forEach(pattern => {
|
||||
let match;
|
||||
while ((match = pattern.exec(prBody)) !== null) {
|
||||
issueNumbers.add(match[1]);
|
||||
}
|
||||
});
|
||||
|
||||
// Also check PR title
|
||||
const prTitle = context.payload.pull_request.title || '';
|
||||
patterns.forEach(pattern => {
|
||||
let match;
|
||||
while ((match = pattern.exec(prTitle)) !== null) {
|
||||
issueNumbers.add(match[1]);
|
||||
}
|
||||
});
|
||||
|
||||
// Also check commit messages in PR
|
||||
const commits = await github.rest.pulls.listCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
commits.data.forEach(commit => {
|
||||
const message = commit.commit.message;
|
||||
patterns.forEach(pattern => {
|
||||
let match;
|
||||
while ((match = pattern.exec(message)) !== null) {
|
||||
issueNumbers.add(match[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const issues = Array.from(issueNumbers);
|
||||
console.log(`Found linked issues: ${issues.join(', ')}`);
|
||||
|
||||
return issues;
|
||||
|
||||
- name: Close linked issues
|
||||
if: steps.extract_issues.outputs.result != '[]'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const issueNumbers = ${{ steps.extract_issues.outputs.result }};
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const prTitle = context.payload.pull_request.title;
|
||||
const prUrl = context.payload.pull_request.html_url;
|
||||
const merger = context.payload.pull_request.merged_by.login;
|
||||
|
||||
console.log(`Processing ${issueNumbers.length} linked issue(s)`);
|
||||
|
||||
for (const issueNumber of issueNumbers) {
|
||||
try {
|
||||
// Get issue details first
|
||||
const issue = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt(issueNumber)
|
||||
});
|
||||
|
||||
// Skip if already closed
|
||||
if (issue.data.state === 'closed') {
|
||||
console.log(`Issue #${issueNumber} already closed, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add comment explaining closure
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt(issueNumber),
|
||||
body: `## ✅ Completed via PR #${prNumber}
|
||||
|
||||
**PR**: ${prTitle}
|
||||
**URL**: ${prUrl}
|
||||
**Merged by**: @${merger}
|
||||
|
||||
This issue has been resolved and the changes have been merged into main.
|
||||
|
||||
🤖 Automatically closed via PR merge automation`
|
||||
});
|
||||
|
||||
// Close the issue
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt(issueNumber),
|
||||
state: 'closed',
|
||||
state_reason: 'completed'
|
||||
});
|
||||
|
||||
console.log(`✅ Closed issue #${issueNumber}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to close issue #${issueNumber}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
- name: Update project board status
|
||||
if: steps.extract_issues.outputs.result != '[]'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const issueNumbers = ${{ steps.extract_issues.outputs.result }};
|
||||
|
||||
for (const issueNumber of issueNumbers) {
|
||||
try {
|
||||
// Add status: done label
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt(issueNumber),
|
||||
labels: ['status: done']
|
||||
});
|
||||
|
||||
// Remove in-progress and in-review labels
|
||||
const labelsToRemove = ['status: in-progress', 'status: in-review'];
|
||||
for (const label of labelsToRemove) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt(issueNumber),
|
||||
name: label
|
||||
});
|
||||
} catch (e) {
|
||||
// Label might not exist, ignore error
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Updated project status for issue #${issueNumber}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to update status for issue #${issueNumber}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
- name: Summary
|
||||
if: steps.extract_issues.outputs.result != '[]'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const issueNumbers = ${{ steps.extract_issues.outputs.result }};
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
console.log(`
|
||||
✅ PR #${prNumber} Merge Automation Complete
|
||||
|
||||
Closed issues: ${issueNumbers.join(', ')}
|
||||
Updated project board: status → done
|
||||
Comments added: Linked to PR #${prNumber}
|
||||
|
||||
All linked issues have been automatically closed and marked as done.
|
||||
`);
|
||||
Reference in New Issue
Block a user