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:
325
.github/AUTOMATION_SETUP.md
vendored
Normal file
325
.github/AUTOMATION_SETUP.md
vendored
Normal file
@@ -0,0 +1,325 @@
|
||||
# GitHub Automation Setup Guide
|
||||
|
||||
**Repository**: claude-skills
|
||||
**Project Number**: 9
|
||||
**Status**: ⚙️ Configuration Required
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This repository includes AI-powered GitHub automation with:
|
||||
|
||||
- ✅ **Claude Code Review** - Automatic PR reviews
|
||||
- ✅ **Auto-Close Issues** - PRs auto-close linked issues when merged
|
||||
- ✅ **Smart Sync** - Bidirectional issue ↔ project board synchronization
|
||||
- ✅ **Quality Gates** - Automated linting, testing, security checks
|
||||
- ✅ **Kill Switch** - Emergency workflow disable capability
|
||||
|
||||
---
|
||||
|
||||
## Quick Start (15 minutes)
|
||||
|
||||
### 1. Create Required Secrets
|
||||
|
||||
You need **2 secrets** for full automation:
|
||||
|
||||
#### ✅ CLAUDE_CODE_OAUTH_TOKEN (Already Configured)
|
||||
|
||||
This secret is already set up for Claude Code reviews.
|
||||
|
||||
#### ⚠️ PROJECTS_TOKEN (Required for Project Board Sync)
|
||||
|
||||
**Create Personal Access Token:**
|
||||
|
||||
1. Go to: https://github.com/settings/tokens/new
|
||||
2. Configure:
|
||||
- **Note**: "Claude Skills Project Board Access"
|
||||
- **Expiration**: 90 days (recommended)
|
||||
- **Select scopes**:
|
||||
- ✅ `repo` (Full control of private repositories)
|
||||
- ✅ `project` (Full control of projects)
|
||||
3. Click "Generate token"
|
||||
4. **Copy the token** (you won't see it again!)
|
||||
|
||||
**Add to Repository:**
|
||||
|
||||
1. Go to: https://github.com/alirezarezvani/claude-skills/settings/secrets/actions
|
||||
2. Click "New repository secret"
|
||||
3. **Name**: `PROJECTS_TOKEN`
|
||||
4. **Value**: [Paste your token]
|
||||
5. Click "Add secret"
|
||||
|
||||
---
|
||||
|
||||
### 2. Create Project Board Labels
|
||||
|
||||
Run these commands to create all required labels:
|
||||
|
||||
```bash
|
||||
# Status Labels (6)
|
||||
gh label create "status: triage" --color "fbca04" --description "To Triage column" --repo alirezarezvani/claude-skills
|
||||
gh label create "status: backlog" --color "d4c5f9" --description "Backlog column" --repo alirezarezvani/claude-skills
|
||||
gh label create "status: ready" --color "0e8a16" --description "Ready column" --repo alirezarezvani/claude-skills
|
||||
gh label create "status: in-progress" --color "1d76db" --description "In Progress column" --repo alirezarezvani/claude-skills
|
||||
gh label create "status: in-review" --color "d876e3" --description "In Review column" --repo alirezarezvani/claude-skills
|
||||
gh label create "status: done" --color "2ea44f" --description "Done column" --repo alirezarezvani/claude-skills
|
||||
|
||||
# Priority Labels (4)
|
||||
gh label create "P0" --color "b60205" --description "Critical priority" --repo alirezarezvani/claude-skills
|
||||
gh label create "P1" --color "d93f0b" --description "High priority" --repo alirezarezvani/claude-skills
|
||||
gh label create "P2" --color "fbca04" --description "Medium priority" --repo alirezarezvani/claude-skills
|
||||
gh label create "P3" --color "0e8a16" --description "Low priority" --repo alirezarezvani/claude-skills
|
||||
|
||||
# Type Labels (already exist - verify)
|
||||
# bug, feature, documentation, enhancement, etc.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Configure Project Board
|
||||
|
||||
Your project board columns must match these exact names:
|
||||
|
||||
1. **To triage**
|
||||
2. **Backlog**
|
||||
3. **Ready**
|
||||
4. **In Progress**
|
||||
5. **In Review**
|
||||
6. **Done**
|
||||
|
||||
**Verify Configuration:**
|
||||
|
||||
1. Go to: https://github.com/users/alirezarezvani/projects/9
|
||||
2. Check column names match exactly (case-sensitive)
|
||||
3. Ensure "Status" field exists
|
||||
|
||||
---
|
||||
|
||||
### 4. Test the Setup
|
||||
|
||||
#### Test 1: Create Test Issue
|
||||
|
||||
```bash
|
||||
gh issue create \
|
||||
--title "Test: Automation Setup" \
|
||||
--body "Testing GitHub automation workflows" \
|
||||
--label "status: triage" \
|
||||
--repo alirezarezvani/claude-skills
|
||||
```
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Issue created
|
||||
- ✅ Auto-added to project board (column: "To triage")
|
||||
- ✅ Label synced
|
||||
|
||||
#### Test 2: Change Issue Status
|
||||
|
||||
```bash
|
||||
# Get the issue number from step 1, then:
|
||||
gh issue edit ISSUE_NUMBER --add-label "status: in-progress" --repo alirezarezvani/claude-skills
|
||||
```
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Issue moved to "In Progress" on project board
|
||||
- ✅ Old status label removed
|
||||
- ✅ New status label applied
|
||||
|
||||
#### Test 3: Create Test PR
|
||||
|
||||
```bash
|
||||
# Create a branch
|
||||
git checkout -b test/automation-setup
|
||||
echo "# Test" > TEST.md
|
||||
git add TEST.md
|
||||
git commit -m "test: verify automation"
|
||||
git push origin test/automation-setup
|
||||
|
||||
# Create PR that fixes the test issue
|
||||
gh pr create \
|
||||
--title "test: Verify automation workflows" \
|
||||
--body "Fixes #ISSUE_NUMBER" \
|
||||
--repo alirezarezvani/claude-skills
|
||||
```
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Claude review triggers (check Actions tab)
|
||||
- ✅ CI Quality Gate runs
|
||||
- ✅ When merged, issue auto-closes
|
||||
- ✅ Project board updates to "Done"
|
||||
|
||||
---
|
||||
|
||||
## Active Workflows
|
||||
|
||||
| Workflow | Trigger | Status |
|
||||
|----------|---------|--------|
|
||||
| **claude-code-review.yml** | PR opened/updated | ✅ Active |
|
||||
| **pr-issue-auto-close.yml** | PR merged | ✅ Active |
|
||||
| **smart-sync.yml** | Issue/board changes | ⚠️ Requires PROJECTS_TOKEN |
|
||||
| **ci-quality-gate.yml** | PR opened/updated | ✅ Active |
|
||||
|
||||
---
|
||||
|
||||
## Emergency Procedures
|
||||
|
||||
### 🚨 Disable All Workflows
|
||||
|
||||
If something goes wrong:
|
||||
|
||||
```bash
|
||||
echo "STATUS: DISABLED" > .github/WORKFLOW_KILLSWITCH
|
||||
git add .github/WORKFLOW_KILLSWITCH
|
||||
git commit -m "emergency: Disable workflows"
|
||||
git push origin main --no-verify
|
||||
```
|
||||
|
||||
All workflows check this file and exit immediately if disabled.
|
||||
|
||||
### ✅ Re-enable Workflows
|
||||
|
||||
```bash
|
||||
echo "STATUS: ENABLED" > .github/WORKFLOW_KILLSWITCH
|
||||
git commit -am "chore: Re-enable workflows"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Auto-Close Issues with PR
|
||||
|
||||
In your PR description:
|
||||
|
||||
```markdown
|
||||
## Summary
|
||||
Fixed the authentication bug
|
||||
|
||||
## Related Issues
|
||||
Fixes #123
|
||||
Closes #456
|
||||
```
|
||||
|
||||
When merged → Issues #123 and #456 automatically close with comment linking to PR.
|
||||
|
||||
### Sync Issue Status
|
||||
|
||||
**Option A - Update Label:**
|
||||
```bash
|
||||
gh issue edit 123 --add-label "status: in-review"
|
||||
```
|
||||
→ Moves to "In Review" on project board
|
||||
|
||||
**Option B - Update Board:**
|
||||
```
|
||||
Drag issue to "In Review" column on project board
|
||||
```
|
||||
→ Adds "status: in-review" label to issue
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Smart Sync Not Working
|
||||
|
||||
**Problem**: Labels not syncing with project board
|
||||
|
||||
**Check:**
|
||||
```bash
|
||||
gh secret list --repo alirezarezvani/claude-skills | grep PROJECTS_TOKEN
|
||||
```
|
||||
|
||||
**Solution**: If missing, add PROJECTS_TOKEN (see Step 1 above)
|
||||
|
||||
### Claude Review Not Running
|
||||
|
||||
**Problem**: No review comment on PR
|
||||
|
||||
**Check:**
|
||||
```bash
|
||||
gh run list --workflow=claude-code-review.yml --limit 5 --repo alirezarezvani/claude-skills
|
||||
```
|
||||
|
||||
**Solutions**:
|
||||
- Verify CLAUDE_CODE_OAUTH_TOKEN exists
|
||||
- Check workflow logs for errors
|
||||
- Re-run workflow from Actions tab
|
||||
|
||||
### Rate Limits
|
||||
|
||||
**Check current limits:**
|
||||
```bash
|
||||
gh api rate_limit --jq '.resources.core.remaining, .resources.graphql.remaining'
|
||||
```
|
||||
|
||||
**Rate Limit Info:**
|
||||
- REST API: 5,000/hour
|
||||
- GraphQL: 5,000/hour
|
||||
- Workflows require 50+ remaining before executing
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Weekly
|
||||
|
||||
```bash
|
||||
# Check failed runs
|
||||
gh run list --status failure --limit 10 --repo alirezarezvani/claude-skills
|
||||
|
||||
# Verify secrets valid
|
||||
gh secret list --repo alirezarezvani/claude-skills
|
||||
```
|
||||
|
||||
### Quarterly
|
||||
|
||||
```bash
|
||||
# Regenerate PROJECTS_TOKEN (expires every 90 days)
|
||||
# 1. Create new token with same scopes
|
||||
# 2. Update repository secret
|
||||
# 3. Test with a sync operation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Notes
|
||||
|
||||
**4-Layer Security Model:**
|
||||
|
||||
1. **GitHub Permissions** - Only team members trigger workflows
|
||||
2. **Tool Restrictions** - Allowlist specific commands only
|
||||
3. **Token Scoping** - Minimal permissions (repo + project)
|
||||
4. **Branch Protection** - Required reviews, status checks
|
||||
|
||||
**Kill Switch**: Emergency disable capability (WORKFLOW_KILLSWITCH file)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ Create PROJECTS_TOKEN secret
|
||||
2. ✅ Create all required labels
|
||||
3. ✅ Verify project board columns
|
||||
4. ✅ Test with sample issue and PR
|
||||
5. ✅ Monitor first few workflow runs
|
||||
6. ✅ Document any project-specific customizations
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
**Documentation:**
|
||||
- This setup guide
|
||||
- Individual workflow files in `.github/workflows/`
|
||||
- Factory reference: https://github.com/alirezarezvani/claude-code-skills-factory
|
||||
|
||||
**Getting Help:**
|
||||
- Create issue with `question` label
|
||||
- Check workflow logs: `gh run view RUN_ID --log`
|
||||
- Review troubleshooting section above
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-04
|
||||
**Status**: Ready for configuration ⚙️
|
||||
1
.github/WORKFLOW_KILLSWITCH
vendored
Normal file
1
.github/WORKFLOW_KILLSWITCH
vendored
Normal file
@@ -0,0 +1 @@
|
||||
STATUS: ENABLED
|
||||
17
.github/branch-protection-config.json
vendored
Normal file
17
.github/branch-protection-config.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"required_status_checks": {
|
||||
"strict": true,
|
||||
"contexts": [
|
||||
"claude-review"
|
||||
]
|
||||
},
|
||||
"enforce_admins": true,
|
||||
"required_pull_request_reviews": null,
|
||||
"restrictions": null,
|
||||
"allow_force_pushes": false,
|
||||
"allow_deletions": false,
|
||||
"block_creations": false,
|
||||
"required_conversation_resolution": true,
|
||||
"required_linear_history": false,
|
||||
"lock_branch": false
|
||||
}
|
||||
14
.github/commit-template.txt
vendored
Normal file
14
.github/commit-template.txt
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<type>(<scope>): <summary>
|
||||
|
||||
## Context
|
||||
-
|
||||
|
||||
## Testing
|
||||
- [ ] All Python scripts tested
|
||||
- [ ] Skills validated with Claude
|
||||
- [ ] Documentation updated
|
||||
- [ ] Examples work as expected
|
||||
|
||||
## Reviewers
|
||||
- [ ] @
|
||||
|
||||
209
.github/pull_request_template.md
vendored
209
.github/pull_request_template.md
vendored
@@ -1,183 +1,60 @@
|
||||
# Pull Request
|
||||
## Summary
|
||||
<!-- Brief description of what this PR accomplishes -->
|
||||
|
||||
## Description
|
||||
|
||||
**What does this PR do?**
|
||||
## Context
|
||||
<!-- Why these changes are needed. Link to issues, discussions, or explain the problem being solved. -->
|
||||
|
||||
A clear and concise description of the changes.
|
||||
|
||||
## Type of Change
|
||||
|
||||
Select the type of change:
|
||||
- [ ] New skill addition
|
||||
- [ ] Skill enhancement/improvement
|
||||
- [ ] Bug fix
|
||||
- [ ] Documentation update
|
||||
- [ ] Python script improvement
|
||||
- [ ] Refactoring/optimization
|
||||
- [ ] Other (specify)
|
||||
|
||||
## Related Issue
|
||||
|
||||
Fixes #(issue number)
|
||||
|
||||
Or: Related to #(issue number)
|
||||
|
||||
---
|
||||
|
||||
## Changes Made
|
||||
|
||||
### Skills Added/Modified
|
||||
|
||||
**Skill Name(s):**
|
||||
-
|
||||
|
||||
**What Changed:**
|
||||
- Added: (new capabilities, scripts, references)
|
||||
- Modified: (what was changed and why)
|
||||
- Removed: (what was deprecated/removed and why)
|
||||
## Changes
|
||||
<!-- High-level overview of what was modified -->
|
||||
|
||||
### Python Scripts
|
||||
|
||||
**New Scripts:**
|
||||
- `script_name.py` - Purpose and functionality
|
||||
|
||||
**Modified Scripts:**
|
||||
- `existing_script.py` - Changes made
|
||||
|
||||
### Documentation
|
||||
|
||||
**Files Updated:**
|
||||
- README.md - (what sections)
|
||||
- SKILL.md - (what changed)
|
||||
- Reference files - (which ones)
|
||||
|
||||
---
|
||||
-
|
||||
|
||||
## Testing
|
||||
<!-- Confirm all checks have passed -->
|
||||
|
||||
### Testing Performed
|
||||
- [ ] Local quality checks passed (Python syntax, markdown links)
|
||||
- [ ] All Python scripts tested and working
|
||||
- [ ] Skills validated with Claude (if applicable)
|
||||
- [ ] `ci-quality-gate` workflow will pass
|
||||
- [ ] Manual testing completed
|
||||
|
||||
- [ ] Tested with Claude AI (uploaded SKILL.md and verified activation)
|
||||
- [ ] Tested with Claude Code (loaded skill and ran workflows)
|
||||
- [ ] All Python scripts run without errors
|
||||
- [ ] Ran scripts with `--help` flag
|
||||
- [ ] Tested JSON output (if applicable)
|
||||
- [ ] All reference links work
|
||||
- [ ] No broken relative paths
|
||||
**Testing Details**:
|
||||
<!-- Describe what you tested and how -->
|
||||
|
||||
### Test Results
|
||||
|
||||
**Claude Activation:**
|
||||
- [ ] Skill activates when appropriate
|
||||
- [ ] Description triggers correctly
|
||||
- [ ] Keywords help discovery
|
||||
## Security
|
||||
<!-- Confirm security requirements met -->
|
||||
|
||||
**Python Scripts:**
|
||||
```bash
|
||||
# Commands run for testing
|
||||
python scripts/tool.py --help
|
||||
python scripts/tool.py test-input.txt
|
||||
```
|
||||
- [ ] No secrets, credentials, or API keys committed
|
||||
- [ ] No destructive commands in generated outputs
|
||||
- [ ] Path traversal vulnerabilities checked
|
||||
- [ ] Dependencies reviewed (if added)
|
||||
|
||||
**Results:**
|
||||
```
|
||||
[Paste test output or describe results]
|
||||
```
|
||||
## Documentation
|
||||
<!-- Confirm documentation updated -->
|
||||
|
||||
- [ ] README.md updated (if applicable)
|
||||
- [ ] CLAUDE.md updated (if applicable)
|
||||
- [ ] Inline code comments added for complex logic
|
||||
- [ ] CHANGELOG.md updated (if applicable)
|
||||
- [ ] Skill SKILL.md files updated (if applicable)
|
||||
|
||||
## Reviewers
|
||||
<!-- Tag reviewers who should look at this PR -->
|
||||
|
||||
- [ ] @
|
||||
|
||||
## Related Issues
|
||||
<!-- Link issues this PR addresses using keywords: Fixes, Closes, Resolves, Related to -->
|
||||
|
||||
Fixes #
|
||||
Closes #
|
||||
Related to #
|
||||
|
||||
---
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
### SKILL.md Quality
|
||||
|
||||
- [ ] YAML frontmatter is valid
|
||||
- [ ] `name` matches directory name
|
||||
- [ ] `description` includes what, when, and keyword triggers
|
||||
- [ ] `license: MIT` included
|
||||
- [ ] `metadata` section complete (version, author, category, domain)
|
||||
- [ ] Keywords section added
|
||||
- [ ] SKILL.md length <200 lines (or justified if longer)
|
||||
- [ ] Clear quick start section
|
||||
- [ ] Core workflows documented
|
||||
- [ ] Examples included
|
||||
|
||||
### Python Scripts Quality (if applicable)
|
||||
|
||||
- [ ] Production-ready code (not placeholders)
|
||||
- [ ] CLI with `--help` support
|
||||
- [ ] Proper error handling
|
||||
- [ ] Clear docstrings
|
||||
- [ ] Type hints used
|
||||
- [ ] Standard library preferred
|
||||
- [ ] Dependencies documented
|
||||
- [ ] No hardcoded paths or credentials
|
||||
|
||||
### Documentation Quality
|
||||
|
||||
- [ ] All links work (no 404s)
|
||||
- [ ] Markdown formatting correct
|
||||
- [ ] No typos or grammar errors
|
||||
- [ ] Code blocks have language specified
|
||||
- [ ] Examples are realistic and complete
|
||||
- [ ] Screenshots included where helpful
|
||||
|
||||
### Repository Integration
|
||||
|
||||
- [ ] Domain-specific README.md updated (if new skill)
|
||||
- [ ] Main README.md updated (if new domain or major feature)
|
||||
- [ ] CLAUDE.md updated (if changes affect development)
|
||||
- [ ] CHANGELOG.md updated (in Unreleased section)
|
||||
|
||||
---
|
||||
|
||||
## ROI & Value
|
||||
|
||||
**Estimated Value of This Contribution:**
|
||||
|
||||
**Time Savings:**
|
||||
- Hours saved per month per user: (estimate)
|
||||
- Number of potential users: (estimate)
|
||||
|
||||
**Quality Improvements:**
|
||||
- Specific quality gains: (describe)
|
||||
- Measurable improvements: (quantify if possible)
|
||||
|
||||
**Why This Matters:**
|
||||
Brief explanation of the business/user value.
|
||||
|
||||
---
|
||||
|
||||
## Screenshots
|
||||
|
||||
If applicable, add screenshots to help explain your changes.
|
||||
|
||||
---
|
||||
|
||||
## Additional Notes
|
||||
|
||||
Any other information reviewers should know:
|
||||
- Implementation decisions made
|
||||
- Alternative approaches considered
|
||||
- Known limitations
|
||||
- Future enhancement ideas
|
||||
|
||||
---
|
||||
|
||||
## Contributor Checklist
|
||||
|
||||
**Before Submitting:**
|
||||
- [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md)
|
||||
- [ ] I have followed the skill creation guidelines
|
||||
- [ ] I have tested thoroughly
|
||||
- [ ] I have updated all relevant documentation
|
||||
- [ ] I have added my changes to CHANGELOG.md (Unreleased section)
|
||||
- [ ] My code follows the repository's style guidelines
|
||||
- [ ] All new Python scripts are production-ready
|
||||
- [ ] I agree to the MIT License for my contributions
|
||||
|
||||
---
|
||||
|
||||
**Thank you for contributing to Claude Skills Library!** 🚀
|
||||
|
||||
Your contribution helps make world-class expertise accessible to everyone through Claude AI.
|
||||
**Type**: <!-- feat | fix | docs | refactor | chore | ci | test | perf | skill -->
|
||||
**Scope**: <!-- component affected (e.g., marketing-skill, workflows, agents, templates) -->
|
||||
|
||||
91
.github/workflows/ci-quality-gate.yml
vendored
Normal file
91
.github/workflows/ci-quality-gate.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
name: CI Quality Gate
|
||||
|
||||
'on':
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: Branch to run quality gate against
|
||||
required: false
|
||||
repository_dispatch:
|
||||
types: [ci-quality]
|
||||
|
||||
concurrency:
|
||||
group: quality-gate-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
name: Lint, Tests, Docs, Security
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Resolve ref
|
||||
id: ref
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ github.event.inputs.ref }}" ]]; then
|
||||
echo "target_ref=${{ github.event.inputs.ref }}" >> "$GITHUB_OUTPUT"
|
||||
elif [[ "${{ github.event_name }}" == "repository_dispatch" && -n "${{ github.event.client_payload.ref }}" ]]; then
|
||||
echo "target_ref=${{ github.event.client_payload.ref }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "target_ref=${{ github.head_ref || github.ref_name }}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.ref.outputs.target_ref }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install tooling
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install yamllint==1.35.1 check-jsonschema==0.28.4 safety==3.2.4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: YAML lint (.github/workflows)
|
||||
run: |
|
||||
yamllint -d '{extends: default, rules: {line-length: {max: 160}}}' .github/workflows
|
||||
|
||||
- name: Validate GitHub workflow schemas
|
||||
run: |
|
||||
check-jsonschema --schema github-workflow --base-dir . .github/workflows/*.yml
|
||||
|
||||
- name: Python syntax check
|
||||
run: |
|
||||
python -m compileall marketing-skill product-team c-level-advisor engineering-team ra-qm-team || true
|
||||
|
||||
- name: Safety dependency audit (requirements*.txt)
|
||||
run: |
|
||||
set -e
|
||||
files=$(find . -name "requirements*.txt" 2>/dev/null || true)
|
||||
if [[ -z "$files" ]]; then
|
||||
echo "No requirements files found; skipping safety scan."
|
||||
exit 0
|
||||
fi
|
||||
for f in $files; do
|
||||
echo "Auditing $f"
|
||||
safety check --full-report --file "$f" || true
|
||||
done
|
||||
|
||||
- name: Markdown link spot-check
|
||||
run: |
|
||||
npx --yes markdown-link-check@3.12.2 README.md
|
||||
|
||||
- name: Summarize results
|
||||
if: always()
|
||||
run: |
|
||||
echo "Quality gate completed with status: ${{ job.status }}"
|
||||
106
.github/workflows/claude-code-review.yml
vendored
106
.github/workflows/claude-code-review.yml
vendored
@@ -1,39 +1,92 @@
|
||||
---
|
||||
name: Claude Code Review
|
||||
|
||||
on:
|
||||
'on':
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
# Optional: Only run on specific file changes
|
||||
# paths:
|
||||
# - "src/**/*.ts"
|
||||
# - "src/**/*.tsx"
|
||||
# - "src/**/*.js"
|
||||
# - "src/**/*.jsx"
|
||||
|
||||
# Prevent multiple review runs on rapid PR updates
|
||||
concurrency:
|
||||
group: claude-review-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
claude-review:
|
||||
# Optional: Filter by PR author
|
||||
# if: |
|
||||
# github.event.pull_request.user.login == 'external-contributor' ||
|
||||
# github.event.pull_request.user.login == 'new-developer' ||
|
||||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
||||
|
||||
# Auto-review ALL pull requests with Claude
|
||||
# BYPASS: Add [EMERGENCY], [SKIP REVIEW], or [HOTFIX] to PR title
|
||||
# BYPASS: Or add 'emergency' or 'skip-review' label to PR
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
id-token: write # Required by Claude Code action for OIDC authentication
|
||||
|
||||
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: Check for Review Bypass
|
||||
id: bypass
|
||||
run: |
|
||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||
PR_LABELS="${{ toJSON(github.event.pull_request.labels.*.name) }}"
|
||||
|
||||
# Check for bypass markers in PR title
|
||||
if echo "$PR_TITLE" | grep -qE '\[EMERGENCY\]|\[SKIP REVIEW\]|\[HOTFIX\]'; then
|
||||
echo "bypass=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=PR title contains bypass marker" >> $GITHUB_OUTPUT
|
||||
echo "⏭️ BYPASS: PR title contains bypass marker"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for bypass labels
|
||||
if echo "$PR_LABELS" | grep -qE 'emergency|skip-review|hotfix'; then
|
||||
echo "bypass=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=PR has bypass label" >> $GITHUB_OUTPUT
|
||||
echo "⏭️ BYPASS: PR has bypass label"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "bypass=false" >> $GITHUB_OUTPUT
|
||||
echo "✅ No bypass detected - review will proceed"
|
||||
|
||||
- name: Post Bypass Notice
|
||||
if: steps.bypass.outputs.bypass == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `## ⏭️ Code Review Bypassed
|
||||
|
||||
**Reason**: ${{ steps.bypass.outputs.reason }}
|
||||
|
||||
⚠️ **Manual review recommended** - This PR was merged without automated code review.
|
||||
|
||||
---
|
||||
*Bypass triggered by emergency procedures protocol*`
|
||||
})
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.bypass.outputs.bypass != 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code Review
|
||||
if: steps.bypass.outputs.bypass != 'true'
|
||||
id: claude-review
|
||||
uses: anthropics/claude-code-action@v1
|
||||
continue-on-error: true
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
prompt: |
|
||||
@@ -46,12 +99,33 @@ jobs:
|
||||
- Performance considerations
|
||||
- Security concerns
|
||||
- Test coverage
|
||||
- Skill quality (if applicable)
|
||||
|
||||
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
||||
|
||||
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
|
||||
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
|
||||
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
|
||||
# or https://docs.claude.com/en/docs/claude-code/cli-reference
|
||||
claude_args: >-
|
||||
--allowed-tools
|
||||
"Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),
|
||||
Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"
|
||||
|
||||
- name: Post fallback review note (quota/timeout)
|
||||
if: steps.claude-review.outcome != 'success'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `## ⚠️ Automated Review Skipped
|
||||
|
||||
The automated Claude review could not complete (likely quota or a transient error).
|
||||
|
||||
- You can retry this workflow from the Actions tab
|
||||
- Proceed with manual review to unblock
|
||||
`
|
||||
})
|
||||
|
||||
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.
|
||||
`);
|
||||
442
.github/workflows/smart-sync.yml
vendored
Normal file
442
.github/workflows/smart-sync.yml
vendored
Normal file
@@ -0,0 +1,442 @@
|
||||
---
|
||||
name: Smart Bidirectional Sync
|
||||
|
||||
'on':
|
||||
issues:
|
||||
types: [labeled, closed, reopened]
|
||||
projects_v2_item:
|
||||
types: [edited]
|
||||
|
||||
# Prevent sync loops with debouncing
|
||||
concurrency:
|
||||
group: smart-sync-${{ github.event.issue.number || github.event.projects_v2_item.node_id }}
|
||||
cancel-in-progress: true # Cancel pending runs (debouncing effect)
|
||||
|
||||
jobs:
|
||||
determine-direction:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 3
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
id-token: write
|
||||
|
||||
outputs:
|
||||
should_sync: ${{ steps.check.outputs.should_sync }}
|
||||
direction: ${{ steps.check.outputs.direction }}
|
||||
issue_number: ${{ steps.check.outputs.issue_number }}
|
||||
|
||||
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: Determine Sync Direction
|
||||
id: check
|
||||
run: |
|
||||
# Check which event triggered this workflow
|
||||
if [ "${{ github.event_name }}" = "issues" ]; then
|
||||
# Issue event → sync to project board
|
||||
echo "direction=issue-to-project" >> $GITHUB_OUTPUT
|
||||
echo "issue_number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
|
||||
|
||||
# Only sync on status label changes or state changes
|
||||
if [[ "${{ github.event.action }}" == "labeled" && "${{ github.event.label.name }}" == status:* ]] || \
|
||||
[ "${{ github.event.action }}" = "closed" ] || \
|
||||
[ "${{ github.event.action }}" = "reopened" ]; then
|
||||
echo "should_sync=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ Will sync: Issue #${{ github.event.issue.number }} → Project Board"
|
||||
else
|
||||
echo "should_sync=false" >> $GITHUB_OUTPUT
|
||||
echo "⏭️ Skipping: Not a status change or state change"
|
||||
fi
|
||||
|
||||
elif [ "${{ github.event_name }}" = "projects_v2_item" ]; then
|
||||
# Project event → sync to issue
|
||||
echo "direction=project-to-issue" >> $GITHUB_OUTPUT
|
||||
echo "should_sync=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ Will sync: Project Board → Issue"
|
||||
|
||||
else
|
||||
echo "should_sync=false" >> $GITHUB_OUTPUT
|
||||
echo "⚠️ Unknown event type"
|
||||
fi
|
||||
|
||||
rate-limit-check:
|
||||
needs: determine-direction
|
||||
if: needs.determine-direction.outputs.should_sync == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
outputs:
|
||||
can_proceed: ${{ steps.limits.outputs.can_proceed }}
|
||||
|
||||
steps:
|
||||
- name: Check Rate Limits (Circuit Breaker)
|
||||
id: limits
|
||||
run: |
|
||||
echo "🔍 Checking GitHub API rate limits..."
|
||||
|
||||
# Get rate limit status
|
||||
core_remaining=$(gh api rate_limit --jq '.resources.core.remaining')
|
||||
core_limit=$(gh api rate_limit --jq '.resources.core.limit')
|
||||
graphql_remaining=$(gh api rate_limit --jq '.resources.graphql.remaining')
|
||||
graphql_limit=$(gh api rate_limit --jq '.resources.graphql.limit')
|
||||
|
||||
echo "📊 Rate Limits:"
|
||||
echo " REST API: $core_remaining/$core_limit"
|
||||
echo " GraphQL: $graphql_remaining/$graphql_limit"
|
||||
|
||||
# Require at least 50 remaining for sync operations
|
||||
if [ "$core_remaining" -lt 50 ] || [ "$graphql_remaining" -lt 50 ]; then
|
||||
echo "can_proceed=false" >> $GITHUB_OUTPUT
|
||||
echo "⚠️ Rate limits too low. Skipping sync to prevent violations."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "can_proceed=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ Rate limits sufficient for sync operation"
|
||||
|
||||
# 10-second debounce delay
|
||||
debounce:
|
||||
needs: [determine-direction, rate-limit-check]
|
||||
if: |
|
||||
needs.determine-direction.outputs.should_sync == 'true' &&
|
||||
needs.rate-limit-check.outputs.can_proceed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
|
||||
steps:
|
||||
- name: Debounce Delay
|
||||
run: |
|
||||
echo "⏱️ Applying 10-second debounce..."
|
||||
sleep 10
|
||||
echo "✅ Debounce complete. Proceeding with sync."
|
||||
|
||||
sync-issue-to-project:
|
||||
needs: [determine-direction, rate-limit-check, debounce]
|
||||
if: |
|
||||
needs.determine-direction.outputs.direction == 'issue-to-project' &&
|
||||
needs.rate-limit-check.outputs.can_proceed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Sync Issue to Project Board
|
||||
uses: anthropics/claude-code-action@v1
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PROJECTS_TOKEN }}
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
|
||||
prompt: |
|
||||
# Issue → Project Board Sync
|
||||
|
||||
**Issue**: #${{ github.event.issue.number }} "${{ github.event.issue.title }}"
|
||||
**State**: ${{ github.event.issue.state }}
|
||||
**Action**: ${{ github.event.action }}
|
||||
|
||||
## Task: Sync issue status to project board
|
||||
|
||||
### Step 1: Check if in Project
|
||||
```bash
|
||||
PROJECT_ITEM=$(gh api graphql -f query='
|
||||
query {
|
||||
repository(owner: "alirezarezvani", name: "claude-skills") {
|
||||
issue(number: ${{ github.event.issue.number }}) {
|
||||
projectItems(first: 10) {
|
||||
nodes {
|
||||
id
|
||||
project { number }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
' --jq '.data.repository.issue.projectItems.nodes[] | select(.project.number == 9) | .id')
|
||||
|
||||
if [ -z "$PROJECT_ITEM" ]; then
|
||||
echo "Adding to project..."
|
||||
gh project item-add 9 --owner alirezarezvani --url ${{ github.event.issue.html_url }}
|
||||
sleep 2
|
||||
|
||||
PROJECT_ITEM=$(gh api graphql -f query='
|
||||
query {
|
||||
repository(owner: "alirezarezvani", name: "claude-skills") {
|
||||
issue(number: ${{ github.event.issue.number }}) {
|
||||
projectItems(first: 10) {
|
||||
nodes {
|
||||
id
|
||||
project { number }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
' --jq '.data.repository.issue.projectItems.nodes[] | select(.project.number == 9) | .id')
|
||||
fi
|
||||
|
||||
echo "Project Item ID: $PROJECT_ITEM"
|
||||
```
|
||||
|
||||
### Step 2: Determine Target Status
|
||||
```bash
|
||||
LABELS=$(gh issue view ${{ github.event.issue.number }} --json labels --jq '[.labels[].name] | join(",")')
|
||||
ISSUE_STATE="${{ github.event.issue.state }}"
|
||||
|
||||
# Priority order: closed state > status labels > default
|
||||
if [ "$ISSUE_STATE" = "closed" ]; then
|
||||
TARGET_STATUS="Done"
|
||||
elif echo "$LABELS" | grep -q "status: done"; then
|
||||
TARGET_STATUS="Done"
|
||||
elif echo "$LABELS" | grep -q "status: in-review"; then
|
||||
TARGET_STATUS="In Review"
|
||||
elif echo "$LABELS" | grep -q "status: in-progress"; then
|
||||
TARGET_STATUS="In Progress"
|
||||
elif echo "$LABELS" | grep -q "status: ready"; then
|
||||
TARGET_STATUS="Ready"
|
||||
elif echo "$LABELS" | grep -q "status: backlog"; then
|
||||
TARGET_STATUS="Backlog"
|
||||
elif echo "$LABELS" | grep -q "status: triage"; then
|
||||
TARGET_STATUS="To triage"
|
||||
else
|
||||
TARGET_STATUS=$([ "$ISSUE_STATE" = "open" ] && echo "To triage" || echo "Done")
|
||||
fi
|
||||
|
||||
echo "Target Status: $TARGET_STATUS"
|
||||
```
|
||||
|
||||
### Step 3: Get Project IDs
|
||||
```bash
|
||||
PROJECT_DATA=$(gh api graphql -f query='
|
||||
query {
|
||||
user(login: "alirezarezvani") {
|
||||
projectV2(number: 9) {
|
||||
id
|
||||
fields(first: 20) {
|
||||
nodes {
|
||||
... on ProjectV2SingleSelectField {
|
||||
id
|
||||
name
|
||||
options {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
')
|
||||
|
||||
PROJECT_ID=$(echo "$PROJECT_DATA" | jq -r '.data.user.projectV2.id')
|
||||
STATUS_FIELD_ID=$(echo "$PROJECT_DATA" | \
|
||||
jq -r '.data.user.projectV2.fields.nodes[] | select(.name == "Status") | .id')
|
||||
STATUS_OPTION_ID=$(echo "$PROJECT_DATA" | jq -r --arg status "$TARGET_STATUS" \
|
||||
'.data.user.projectV2.fields.nodes[] | select(.name == "Status") | .options[] | select(.name == $status) | .id')
|
||||
```
|
||||
|
||||
### Step 4: Update Project Board
|
||||
```bash
|
||||
if [ -n "$PROJECT_ITEM" ] && [ -n "$STATUS_OPTION_ID" ]; then
|
||||
gh api graphql -f query='
|
||||
mutation {
|
||||
updateProjectV2ItemFieldValue(
|
||||
input: {
|
||||
projectId: "'"$PROJECT_ID"'"
|
||||
itemId: "'"$PROJECT_ITEM"'"
|
||||
fieldId: "'"$STATUS_FIELD_ID"'"
|
||||
value: { singleSelectOptionId: "'"$STATUS_OPTION_ID"'" }
|
||||
}
|
||||
) {
|
||||
projectV2Item { id }
|
||||
}
|
||||
}
|
||||
'
|
||||
echo "✅ Project board updated to: $TARGET_STATUS"
|
||||
else
|
||||
echo "⚠️ Could not update (missing IDs)"
|
||||
fi
|
||||
```
|
||||
|
||||
## Rules
|
||||
- DO NOT comment on issue (prevents notification spam)
|
||||
- DO NOT modify issue labels (prevents sync loop)
|
||||
- Only update project board status
|
||||
|
||||
claude_args: '--allowed-tools "Bash(gh issue:*),Bash(gh api:*),Bash(gh project:*),Bash(echo:*),Bash(sleep:*)"'
|
||||
|
||||
sync-project-to-issue:
|
||||
needs: [determine-direction, rate-limit-check, debounce]
|
||||
if: |
|
||||
needs.determine-direction.outputs.direction == 'project-to-issue' &&
|
||||
needs.rate-limit-check.outputs.can_proceed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Sync Project Board to Issue
|
||||
uses: anthropics/claude-code-action@v1
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PROJECTS_TOKEN }}
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
|
||||
prompt: |
|
||||
# Project Board → Issue Sync
|
||||
|
||||
**Project Item**: ${{ github.event.projects_v2_item.node_id }}
|
||||
**Content**: ${{ github.event.projects_v2_item.content_node_id }}
|
||||
**Changed By**: @${{ github.event.sender.login }}
|
||||
|
||||
## Task: Sync project board status to issue
|
||||
|
||||
### Step 1: Get Issue Number
|
||||
```bash
|
||||
CONTENT_ID="${{ github.event.projects_v2_item.content_node_id }}"
|
||||
|
||||
ISSUE_DATA=$(gh api graphql -f query='
|
||||
query {
|
||||
node(id: "${{ github.event.projects_v2_item.node_id }}") {
|
||||
... on ProjectV2Item {
|
||||
content {
|
||||
... on Issue {
|
||||
number
|
||||
url
|
||||
state
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
')
|
||||
|
||||
ISSUE_NUMBER=$(echo "$ISSUE_DATA" | jq -r '.data.node.content.number')
|
||||
|
||||
if [ -z "$ISSUE_NUMBER" ] || [ "$ISSUE_NUMBER" = "null" ]; then
|
||||
echo "⏭️ Not an issue (might be PR or other content)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Issue Number: $ISSUE_NUMBER"
|
||||
```
|
||||
|
||||
### Step 2: Get Project Status
|
||||
```bash
|
||||
STATUS=$(gh api graphql -f query='
|
||||
query {
|
||||
node(id: "${{ github.event.projects_v2_item.node_id }}") {
|
||||
... on ProjectV2Item {
|
||||
fieldValues(first: 20) {
|
||||
nodes {
|
||||
... on ProjectV2ItemFieldSingleSelectValue {
|
||||
name
|
||||
field {
|
||||
... on ProjectV2SingleSelectField {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
' --jq '.data.node.fieldValues.nodes[] | select(.field.name == "Status") | .name')
|
||||
|
||||
if [ -z "$STATUS" ]; then
|
||||
echo "⏭️ No status field found"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Project Status: $STATUS"
|
||||
```
|
||||
|
||||
### Step 3: Map Status to Label
|
||||
```bash
|
||||
case "$STATUS" in
|
||||
"To triage") NEW_LABEL="status: triage" ;;
|
||||
"Backlog") NEW_LABEL="status: backlog" ;;
|
||||
"Ready") NEW_LABEL="status: ready" ;;
|
||||
"In Progress") NEW_LABEL="status: in-progress" ;;
|
||||
"In Review") NEW_LABEL="status: in-review" ;;
|
||||
"Done") NEW_LABEL="status: done" ;;
|
||||
*)
|
||||
echo "⏭️ Unknown status: $STATUS"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Target Label: $NEW_LABEL"
|
||||
```
|
||||
|
||||
### Step 4: Update Issue Labels
|
||||
```bash
|
||||
CURRENT_LABELS=$(gh issue view $ISSUE_NUMBER --json labels --jq '[.labels[].name] | join(",")')
|
||||
|
||||
# Remove all status: labels
|
||||
for label in "status: triage" "status: backlog" "status: ready" "status: in-progress" "status: in-review" "status: done"; do
|
||||
if echo "$CURRENT_LABELS" | grep -q "$label"; then
|
||||
gh issue edit $ISSUE_NUMBER --remove-label "$label" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Add new status label
|
||||
gh issue edit $ISSUE_NUMBER --add-label "$NEW_LABEL"
|
||||
echo "✅ Label updated to: $NEW_LABEL"
|
||||
```
|
||||
|
||||
### Step 5: Handle Issue State
|
||||
```bash
|
||||
CURRENT_STATE=$(gh issue view $ISSUE_NUMBER --json state --jq '.state')
|
||||
|
||||
if [ "$STATUS" = "Done" ] && [ "$CURRENT_STATE" = "OPEN" ]; then
|
||||
gh issue close $ISSUE_NUMBER --reason completed
|
||||
echo "✅ Issue closed (moved to Done)"
|
||||
elif [ "$STATUS" != "Done" ] && [ "$CURRENT_STATE" = "CLOSED" ]; then
|
||||
gh issue reopen $ISSUE_NUMBER
|
||||
echo "✅ Issue reopened (moved from Done)"
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 6: Silent Completion
|
||||
```bash
|
||||
echo "✅ Sync complete: Issue #$ISSUE_NUMBER updated to $STATUS"
|
||||
```
|
||||
|
||||
## Rules
|
||||
- DO NOT comment on issue (prevents notification spam)
|
||||
- DO NOT modify project board (prevents sync loop)
|
||||
- Only update issue labels and state
|
||||
|
||||
claude_args: '--allowed-tools "Bash(gh issue:*),Bash(gh api:*),Bash(echo:*)"'
|
||||
Reference in New Issue
Block a user