Add BlackRoad Completion Framework
- GitHub Actions workflows (auto-merge, branch-tracker, issue-to-board, stale-cleanup) - Issue templates (agent-task, bug, task) - PR template - Automation scripts (slack-to-github, create-issue) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
85
.github/ISSUE_TEMPLATE/agent-task.yml
vendored
Normal file
85
.github/ISSUE_TEMPLATE/agent-task.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
name: "🤖 Agent Task"
|
||||||
|
description: "For Codex or other agents to pick up"
|
||||||
|
title: "[AGENT] "
|
||||||
|
labels: ["agent-task", "automated"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Agent-Executable Task
|
||||||
|
This issue is designed to be picked up and executed by Codex or another AI agent.
|
||||||
|
|
||||||
|
Keep instructions clear, specific, and executable.
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: priority
|
||||||
|
attributes:
|
||||||
|
label: Priority
|
||||||
|
options:
|
||||||
|
- "P0 - Immediate"
|
||||||
|
- "P1 - Today"
|
||||||
|
- "P2 - This week"
|
||||||
|
- "P3 - When available"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: instruction
|
||||||
|
attributes:
|
||||||
|
label: Instruction
|
||||||
|
description: What should the agent do? Be specific and executable.
|
||||||
|
placeholder: |
|
||||||
|
Create a new API endpoint at /api/users/preferences that:
|
||||||
|
- Accepts GET and PUT requests
|
||||||
|
- GET returns current user preferences from database
|
||||||
|
- PUT updates preferences with validation
|
||||||
|
- Follow existing patterns in /api/users/profile
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: files
|
||||||
|
attributes:
|
||||||
|
label: Files to touch
|
||||||
|
description: Which files should be created or modified?
|
||||||
|
placeholder: |
|
||||||
|
- Create: src/pages/api/users/preferences.ts
|
||||||
|
- Modify: src/types/user.ts (add PreferencesType)
|
||||||
|
- Modify: prisma/schema.prisma (if needed)
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: done
|
||||||
|
attributes:
|
||||||
|
label: Definition of done
|
||||||
|
description: How does the agent know it's complete?
|
||||||
|
placeholder: |
|
||||||
|
- Endpoint responds correctly to GET/PUT
|
||||||
|
- TypeScript compiles without errors
|
||||||
|
- Tests pass (if tests exist for this area)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: constraints
|
||||||
|
attributes:
|
||||||
|
label: Constraints / Don'ts
|
||||||
|
description: What should the agent avoid?
|
||||||
|
placeholder: |
|
||||||
|
- Don't modify the auth middleware
|
||||||
|
- Don't add new dependencies
|
||||||
|
- Follow existing code style
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: agent
|
||||||
|
attributes:
|
||||||
|
label: Assigned agent
|
||||||
|
options:
|
||||||
|
- "Codex"
|
||||||
|
- "Any available agent"
|
||||||
|
- "Specific agent (note in context)"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
89
.github/ISSUE_TEMPLATE/bug.yml
vendored
89
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -1,102 +1,71 @@
|
|||||||
name: Bug Report
|
name: "🐛 Bug"
|
||||||
description: Report a bug in the Prism Console
|
description: "Something is broken"
|
||||||
title: "[BUG] "
|
title: "[BUG] "
|
||||||
labels: ["type:bug", "team:prism", "status:ready"]
|
labels: ["bug"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
## Bug Report Template
|
## Bug Report
|
||||||
Use this template to report bugs in the Prism Console.
|
What's broken? Let's fix it.
|
||||||
|
|
||||||
- type: dropdown
|
|
||||||
id: priority
|
|
||||||
attributes:
|
|
||||||
label: Priority
|
|
||||||
description: How critical is this bug?
|
|
||||||
options:
|
|
||||||
- prio:P0
|
|
||||||
- prio:P1
|
|
||||||
- prio:P2
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: severity
|
id: severity
|
||||||
attributes:
|
attributes:
|
||||||
label: Severity
|
label: Severity
|
||||||
description: What is the impact of this bug?
|
description: How bad is it?
|
||||||
options:
|
options:
|
||||||
- Critical - Console unusable
|
- "🔥 Critical - Production down, data loss, security issue"
|
||||||
- High - Major feature broken
|
- "🟠 High - Major feature broken, no workaround"
|
||||||
- Medium - Feature partially broken
|
- "🟡 Medium - Feature broken but has workaround"
|
||||||
- Low - Minor issue or cosmetic
|
- "🟢 Low - Minor issue, cosmetic, edge case"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: what
|
||||||
attributes:
|
attributes:
|
||||||
label: Bug Description
|
label: What's broken?
|
||||||
description: A clear description of the bug
|
description: One sentence description
|
||||||
placeholder: "The Prism Console crashes when loading the agents page..."
|
placeholder: "Login button doesn't work on mobile Safari"
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: reproduction
|
|
||||||
attributes:
|
|
||||||
label: Steps to Reproduce
|
|
||||||
description: How can we reproduce this bug?
|
|
||||||
placeholder: |
|
|
||||||
1. Navigate to /agents
|
|
||||||
2. Click on 'atlas' agent
|
|
||||||
3. Console crashes
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: expected
|
id: expected
|
||||||
attributes:
|
attributes:
|
||||||
label: Expected Behavior
|
label: Expected behavior
|
||||||
description: What should happen?
|
description: What should happen?
|
||||||
placeholder: "The agent inspector should open..."
|
placeholder: "Clicking login should open the auth modal"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: actual
|
id: actual
|
||||||
attributes:
|
attributes:
|
||||||
label: Actual Behavior
|
label: Actual behavior
|
||||||
description: What actually happens?
|
description: What actually happens?
|
||||||
placeholder: "Console crashes with error..."
|
placeholder: "Nothing happens. Console shows: TypeError..."
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: environment
|
|
||||||
attributes:
|
|
||||||
label: Environment
|
|
||||||
description: Where does this occur? (Railway, local, Cloudflare, etc.)
|
|
||||||
placeholder: "Railway production"
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: logs
|
id: repro
|
||||||
attributes:
|
attributes:
|
||||||
label: Error Logs
|
label: How to reproduce
|
||||||
description: Paste any relevant error logs or console output
|
description: Steps to trigger the bug
|
||||||
placeholder: "Error: Cannot read property..."
|
placeholder: |
|
||||||
render: shell
|
1. Open site on iPhone Safari
|
||||||
|
2. Tap Login button
|
||||||
|
3. Nothing happens
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: context
|
id: context
|
||||||
attributes:
|
attributes:
|
||||||
label: Additional Context
|
label: Environment / Context
|
||||||
description: Any other relevant information
|
description: Browser, OS, device, account type, etc.
|
||||||
placeholder: "Screenshots, browser version, etc..."
|
placeholder: "iOS 17, Safari, iPhone 15 Pro"
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
blank_issues_enabled: true
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: BlackRoad OS Docs
|
- name: "💬 Quick question / discussion"
|
||||||
url: https://github.com/BlackRoad-OS/blackroad-os-docs
|
url: https://github.com/YOUR_USERNAME/YOUR_REPO/discussions
|
||||||
about: Check the docs before opening an issue
|
about: "For questions, ideas, or discussions that aren't actionable tasks yet"
|
||||||
|
|||||||
65
.github/ISSUE_TEMPLATE/task.yml
vendored
Normal file
65
.github/ISSUE_TEMPLATE/task.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
name: "🎯 Task"
|
||||||
|
description: "Standard work item - features, changes, improvements"
|
||||||
|
title: "[TASK] "
|
||||||
|
labels: ["task"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## Quick Task Creation
|
||||||
|
Keep it simple. One task = one thing to do.
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: priority
|
||||||
|
attributes:
|
||||||
|
label: Priority
|
||||||
|
description: How urgent is this?
|
||||||
|
options:
|
||||||
|
- "P0 - Do it now (blocks everything)"
|
||||||
|
- "P1 - Today"
|
||||||
|
- "P2 - This week"
|
||||||
|
- "P3 - Backlog"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: what
|
||||||
|
attributes:
|
||||||
|
label: What needs to happen?
|
||||||
|
description: One sentence. If it's more than one sentence, split into multiple issues.
|
||||||
|
placeholder: "Add a logout button to the nav bar"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: done
|
||||||
|
attributes:
|
||||||
|
label: Definition of done
|
||||||
|
description: How do we know this is complete?
|
||||||
|
placeholder: |
|
||||||
|
- Logout button visible in nav
|
||||||
|
- Clicking it clears session and redirects to /login
|
||||||
|
- Works on mobile
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Context (optional)
|
||||||
|
description: Any additional info that helps
|
||||||
|
placeholder: "Related to issue #45. See Figma design at..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: assignee
|
||||||
|
attributes:
|
||||||
|
label: Who should handle this?
|
||||||
|
description: Human or agent?
|
||||||
|
options:
|
||||||
|
- "Codex / Agent"
|
||||||
|
- "Human (me)"
|
||||||
|
- "Unassigned - triage needed"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
49
.github/PULL_REQUEST_TEMPLATE.md
vendored
49
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,39 +1,22 @@
|
|||||||
## Summary
|
## What
|
||||||
<!-- What does this PR do? One paragraph max. -->
|
|
||||||
|
<!-- One line: what does this PR do? -->
|
||||||
|
|
||||||
|
## Linked Issue
|
||||||
|
|
||||||
|
Fixes #
|
||||||
|
|
||||||
|
<!-- The "Fixes #123" syntax auto-closes the issue when merged -->
|
||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
<!-- Bulleted list of specific changes -->
|
|
||||||
-
|
|
||||||
|
|
||||||
## Type
|
<!-- Optional: bullet list of key changes if not obvious -->
|
||||||
<!-- Check one -->
|
|
||||||
- [ ] Feature
|
|
||||||
- [ ] Bug fix
|
|
||||||
- [ ] Infrastructure / CI
|
|
||||||
- [ ] Documentation
|
|
||||||
- [ ] Refactor
|
|
||||||
- [ ] Config change
|
|
||||||
|
|
||||||
## Tests
|
---
|
||||||
<!-- How was this tested? -->
|
|
||||||
- [ ] Unit tests pass
|
|
||||||
- [ ] Integration tests pass
|
|
||||||
- [ ] Manual testing (describe below)
|
|
||||||
- [ ] N/A (docs only)
|
|
||||||
|
|
||||||
**Manual testing steps:**
|
<!--
|
||||||
|
This PR will auto-merge when CI passes.
|
||||||
|
No manual approval required.
|
||||||
|
|
||||||
|
If CI fails, fix it or add the 'blocked' label if you need help.
|
||||||
## Risk / Impact
|
-->
|
||||||
<!-- What could go wrong? What's the blast radius? -->
|
|
||||||
- Risk level: Low / Medium / High
|
|
||||||
- Affected services:
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
- [ ] Code follows project conventions
|
|
||||||
- [ ] No secrets or credentials committed
|
|
||||||
- [ ] README updated (if applicable)
|
|
||||||
- [ ] This is a single logical change (atomic PR)
|
|
||||||
|
|
||||||
## Related Issues
|
|
||||||
<!-- Closes #123, Relates to #456 -->
|
|
||||||
|
|||||||
82
.github/workflows/auto-merge.yml
vendored
82
.github/workflows/auto-merge.yml
vendored
@@ -1,12 +1,19 @@
|
|||||||
name: Auto Merge
|
name: Auto-Approve and Merge
|
||||||
|
|
||||||
|
# This workflow automatically approves and merges PRs when:
|
||||||
|
# 1. CI passes
|
||||||
|
# 2. PR is from a trusted source (you, Codex, or designated bots)
|
||||||
|
#
|
||||||
|
# No human approval required. CI is the reviewer.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened, labeled]
|
types: [opened, synchronize, reopened]
|
||||||
pull_request_review:
|
|
||||||
types: [submitted]
|
|
||||||
check_suite:
|
check_suite:
|
||||||
types: [completed]
|
types: [completed]
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["CI"] # Replace with your actual CI workflow name
|
||||||
|
types: [completed]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -15,23 +22,60 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
auto-merge:
|
auto-merge:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Only run for trusted actors
|
||||||
|
# Add your GitHub username, Codex bot, any other trusted sources
|
||||||
if: |
|
if: |
|
||||||
(github.event.pull_request.user.login == 'dependabot[bot]' ||
|
github.actor == 'YOUR_GITHUB_USERNAME' ||
|
||||||
contains(github.event.pull_request.labels.*.name, 'automerge')) &&
|
github.actor == 'codex-bot' ||
|
||||||
github.event.pull_request.draft == false
|
github.actor == 'dependabot[bot]' ||
|
||||||
|
github.actor == 'github-actions[bot]'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Enable auto-merge
|
- name: Checkout
|
||||||
run: |
|
uses: actions/checkout@v4
|
||||||
echo "🤖 Auto-merge conditions met - enabling auto-merge"
|
|
||||||
gh pr merge --auto --squash "$PR_URL" || echo "Auto-merge may already be enabled or checks not ready"
|
|
||||||
env:
|
|
||||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Comment on PR
|
- name: Wait for CI to complete
|
||||||
run: |
|
uses: fountainhead/action-wait-for-check@v1.1.0
|
||||||
gh pr comment "$PR_URL" --body "🤖 Auto-merge enabled. PR will merge when all required checks pass and approvals are met."
|
id: wait-for-ci
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
checkName: build # Replace with your CI check name
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
timeoutSeconds: 300
|
||||||
|
intervalSeconds: 10
|
||||||
|
|
||||||
|
- name: Auto-approve PR
|
||||||
|
if: steps.wait-for-ci.outputs.conclusion == 'success'
|
||||||
|
uses: hmarr/auto-approve-action@v4
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Auto-merge PR
|
||||||
|
if: steps.wait-for-ci.outputs.conclusion == 'success'
|
||||||
|
uses: pascalgn/automerge-action@v0.16.2
|
||||||
env:
|
env:
|
||||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
MERGE_METHOD: squash
|
||||||
|
MERGE_COMMIT_MESSAGE: pull-request-title
|
||||||
|
MERGE_DELETE_BRANCH: true
|
||||||
|
UPDATE_METHOD: rebase
|
||||||
|
|
||||||
|
- name: Add blocked label on CI failure
|
||||||
|
if: steps.wait-for-ci.outputs.conclusion == 'failure'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.pull_request.number,
|
||||||
|
labels: ['blocked', 'ci-failed']
|
||||||
|
});
|
||||||
|
|
||||||
|
github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.pull_request.number,
|
||||||
|
body: '🔴 **CI Failed** - Auto-merge blocked. Check the logs and fix the issue.'
|
||||||
|
});
|
||||||
|
|||||||
83
.github/workflows/branch-tracker.yml
vendored
Normal file
83
.github/workflows/branch-tracker.yml
vendored
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
name: Track Branch Activity
|
||||||
|
|
||||||
|
# When a branch is created that matches an issue number,
|
||||||
|
# automatically update the project board to show work has started
|
||||||
|
|
||||||
|
on:
|
||||||
|
create:
|
||||||
|
branches:
|
||||||
|
- 'issue-*'
|
||||||
|
- 'fix-*'
|
||||||
|
- 'feat-*'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'issue-*'
|
||||||
|
- 'fix-*'
|
||||||
|
- 'feat-*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
repository-projects: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
track-branch:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Extract issue number from branch name
|
||||||
|
id: extract
|
||||||
|
run: |
|
||||||
|
BRANCH_NAME="${{ github.ref_name }}"
|
||||||
|
echo "Branch: $BRANCH_NAME"
|
||||||
|
|
||||||
|
# Extract issue number from branch name
|
||||||
|
# Supports: issue-123-description, fix-123-bug, feat-123-feature
|
||||||
|
ISSUE_NUM=$(echo "$BRANCH_NAME" | grep -oP '(?<=issue-|fix-|feat-)\d+' | head -1)
|
||||||
|
|
||||||
|
if [ -n "$ISSUE_NUM" ]; then
|
||||||
|
echo "issue_number=$ISSUE_NUM" >> $GITHUB_OUTPUT
|
||||||
|
echo "Found issue number: $ISSUE_NUM"
|
||||||
|
else
|
||||||
|
echo "No issue number found in branch name"
|
||||||
|
echo "issue_number=" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Add 'in-progress' label to linked issue
|
||||||
|
if: steps.extract.outputs.issue_number != ''
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const issueNumber = ${{ steps.extract.outputs.issue_number }};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Add in-progress label
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
labels: ['in-progress']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove triage/inbox labels if present
|
||||||
|
try {
|
||||||
|
await github.rest.issues.removeLabel({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
name: 'triage'
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Label might not exist, that's fine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add comment showing work has started
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
body: `🚀 **Work started** on branch \`${{ github.ref_name }}\`\n\nActor: ${{ github.actor }}`
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Updated issue #${issueNumber} - work in progress`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Could not update issue #${issueNumber}: ${error.message}`);
|
||||||
|
}
|
||||||
56
.github/workflows/issue-to-board.yml
vendored
Normal file
56
.github/workflows/issue-to-board.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: Add Issue to Project Board
|
||||||
|
|
||||||
|
# Automatically adds new issues to your GitHub Project board
|
||||||
|
# No manual card creation needed
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
repository-projects: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Replace with your Project number (find in Project URL)
|
||||||
|
# Example: https://github.com/users/YOUR_USERNAME/projects/1 → PROJECT_NUMBER=1
|
||||||
|
PROJECT_NUMBER: 1
|
||||||
|
|
||||||
|
# Replace with your GitHub username or org
|
||||||
|
PROJECT_OWNER: YOUR_GITHUB_USERNAME
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
add-to-project:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Add issue to project
|
||||||
|
uses: actions/add-to-project@v0.5.0
|
||||||
|
with:
|
||||||
|
project-url: https://github.com/users/${{ env.PROJECT_OWNER }}/projects/${{ env.PROJECT_NUMBER }}
|
||||||
|
github-token: ${{ secrets.PROJECT_TOKEN }}
|
||||||
|
# Note: You need a PAT with project permissions, not GITHUB_TOKEN
|
||||||
|
# Create one at: Settings → Developer settings → Personal access tokens
|
||||||
|
# Scopes needed: project, repo
|
||||||
|
|
||||||
|
- name: Set initial status to Inbox
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.PROJECT_TOKEN }}
|
||||||
|
script: |
|
||||||
|
// This sets the Status field to "Inbox" on the project board
|
||||||
|
// You'll need to customize field IDs for your specific project
|
||||||
|
|
||||||
|
console.log('Issue #${{ github.event.issue.number }} added to project board');
|
||||||
|
console.log('Title: ${{ github.event.issue.title }}');
|
||||||
|
|
||||||
|
- name: Add triage label if no labels
|
||||||
|
if: github.event.issue.labels[0] == null
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
labels: ['triage']
|
||||||
|
});
|
||||||
69
.github/workflows/stale-cleanup.yml
vendored
Normal file
69
.github/workflows/stale-cleanup.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
name: Stale Issue Cleanup
|
||||||
|
|
||||||
|
# Automatically flags and closes stale issues
|
||||||
|
# Keeps your board clean without manual gardening
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Run daily at midnight UTC
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
# Allow manual trigger
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Mark and close stale issues
|
||||||
|
uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
# Issues
|
||||||
|
stale-issue-message: |
|
||||||
|
⏰ **This issue has been inactive for 14 days.**
|
||||||
|
|
||||||
|
If this is still needed, please comment or update the issue.
|
||||||
|
Otherwise, it will be closed in 7 days.
|
||||||
|
|
||||||
|
To keep this open: add a comment or remove the `stale` label.
|
||||||
|
close-issue-message: |
|
||||||
|
🗂️ **Closed due to inactivity.**
|
||||||
|
|
||||||
|
If this is still needed, reopen the issue or create a new one.
|
||||||
|
stale-issue-label: 'stale'
|
||||||
|
days-before-issue-stale: 14
|
||||||
|
days-before-issue-close: 7
|
||||||
|
|
||||||
|
# PRs - more aggressive since they should flow fast
|
||||||
|
stale-pr-message: |
|
||||||
|
⏰ **This PR has been inactive for 7 days.**
|
||||||
|
|
||||||
|
If CI is failing, fix it. If it's blocked, add the `blocked` label.
|
||||||
|
Otherwise, this PR will be closed in 3 days.
|
||||||
|
close-pr-message: |
|
||||||
|
🗂️ **Closed due to inactivity.**
|
||||||
|
|
||||||
|
If this work is still needed, create a new PR.
|
||||||
|
stale-pr-label: 'stale'
|
||||||
|
days-before-pr-stale: 7
|
||||||
|
days-before-pr-close: 3
|
||||||
|
|
||||||
|
# Exemptions
|
||||||
|
exempt-issue-labels: 'pinned,security,blocked,p0-now'
|
||||||
|
exempt-pr-labels: 'pinned,security,blocked'
|
||||||
|
|
||||||
|
# Don't mark issues that have recent commits on linked branches
|
||||||
|
exempt-all-pr-milestones: true
|
||||||
|
|
||||||
|
# Operations per run (GitHub API limits)
|
||||||
|
operations-per-run: 100
|
||||||
|
|
||||||
|
- name: Report cleanup stats
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
console.log('Stale cleanup completed');
|
||||||
|
console.log('Check the Actions log for details on marked/closed items');
|
||||||
123
scripts/create-issue.sh
Normal file
123
scripts/create-issue.sh
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# create-issue.sh
|
||||||
|
# Quick issue creation from the command line
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./create-issue.sh "Fix the login redirect bug"
|
||||||
|
# ./create-issue.sh "Add dark mode toggle" --priority p1
|
||||||
|
# ./create-issue.sh "Refactor auth module" --agent
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - GitHub CLI (gh) installed and authenticated
|
||||||
|
# - Run from within a git repo
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
PRIORITY="P2"
|
||||||
|
LABELS="task"
|
||||||
|
AGENT_MODE=false
|
||||||
|
TITLE=""
|
||||||
|
BODY=""
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--priority|-p)
|
||||||
|
PRIORITY="${2^^}" # Uppercase
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--agent|-a)
|
||||||
|
AGENT_MODE=true
|
||||||
|
LABELS="agent-task,automated"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--bug|-b)
|
||||||
|
LABELS="bug"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--body)
|
||||||
|
BODY="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 \"Issue title\" [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --priority, -p <P0|P1|P2|P3> Set priority (default: P2)"
|
||||||
|
echo " --agent, -a Mark as agent task"
|
||||||
|
echo " --bug, -b Mark as bug"
|
||||||
|
echo " --body \"text\" Add body text"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 \"Fix login redirect\""
|
||||||
|
echo " $0 \"Add dark mode\" --priority p1"
|
||||||
|
echo " $0 \"Refactor auth\" --agent --priority p0"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -z "$TITLE" ]]; then
|
||||||
|
TITLE="$1"
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validate
|
||||||
|
if [[ -z "$TITLE" ]]; then
|
||||||
|
echo -e "${RED}Error: Issue title required${NC}"
|
||||||
|
echo "Usage: $0 \"Issue title\" [options]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check gh is installed
|
||||||
|
if ! command -v gh &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: GitHub CLI (gh) not installed${NC}"
|
||||||
|
echo "Install: https://cli.github.com/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the issue
|
||||||
|
echo -e "${YELLOW}Creating issue...${NC}"
|
||||||
|
|
||||||
|
# Add priority label
|
||||||
|
case $PRIORITY in
|
||||||
|
P0) LABELS="$LABELS,p0-now" ;;
|
||||||
|
P1) LABELS="$LABELS,p1-today" ;;
|
||||||
|
P2) LABELS="$LABELS,p2-week" ;;
|
||||||
|
P3) LABELS="$LABELS,p3-backlog" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Construct title prefix
|
||||||
|
if [[ "$LABELS" == *"agent-task"* ]]; then
|
||||||
|
FULL_TITLE="[AGENT] $TITLE"
|
||||||
|
elif [[ "$LABELS" == *"bug"* ]]; then
|
||||||
|
FULL_TITLE="[BUG] $TITLE"
|
||||||
|
else
|
||||||
|
FULL_TITLE="[TASK] $TITLE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create the issue
|
||||||
|
if [[ -n "$BODY" ]]; then
|
||||||
|
ISSUE_URL=$(gh issue create --title "$FULL_TITLE" --body "$BODY" --label "$LABELS" 2>&1)
|
||||||
|
else
|
||||||
|
ISSUE_URL=$(gh issue create --title "$FULL_TITLE" --body "Created via CLI" --label "$LABELS" 2>&1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ Issue created${NC}"
|
||||||
|
echo "$ISSUE_URL"
|
||||||
|
|
||||||
|
# Extract issue number for convenience
|
||||||
|
ISSUE_NUM=$(echo "$ISSUE_URL" | grep -oP '\d+$')
|
||||||
|
if [[ -n "$ISSUE_NUM" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "Branch name: ${YELLOW}issue-$ISSUE_NUM-$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-' | head -c 30)${NC}"
|
||||||
|
fi
|
||||||
183
scripts/slack-to-github.js
Normal file
183
scripts/slack-to-github.js
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
/**
|
||||||
|
* slack-to-github.js
|
||||||
|
*
|
||||||
|
* Webhook handler that creates GitHub issues from Slack messages.
|
||||||
|
* Deploy as: Cloudflare Worker, Vercel Function, or any serverless platform.
|
||||||
|
*
|
||||||
|
* Trigger: Slack slash command or bot mention
|
||||||
|
* Example: /issue Fix the login redirect bug
|
||||||
|
* Example: @blackroad-bot create issue: Add dark mode toggle
|
||||||
|
*
|
||||||
|
* Setup:
|
||||||
|
* 1. Create a Slack App with slash commands or bot
|
||||||
|
* 2. Set the request URL to your deployed function
|
||||||
|
* 3. Set environment variables: GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO, SLACK_SIGNING_SECRET
|
||||||
|
*/
|
||||||
|
|
||||||
|
// For Cloudflare Workers
|
||||||
|
export default {
|
||||||
|
async fetch(request, env) {
|
||||||
|
return handleRequest(request, env);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For Vercel/other platforms, use:
|
||||||
|
// export default async function handler(req, res) { ... }
|
||||||
|
|
||||||
|
async function handleRequest(request, env) {
|
||||||
|
// Verify this is a POST request
|
||||||
|
if (request.method !== 'POST') {
|
||||||
|
return new Response('Method not allowed', { status: 405 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Parse the Slack payload
|
||||||
|
const formData = await request.formData();
|
||||||
|
const payload = Object.fromEntries(formData);
|
||||||
|
|
||||||
|
// For slash commands, the text is in payload.text
|
||||||
|
// For bot mentions, you'd parse payload differently
|
||||||
|
const text = payload.text || '';
|
||||||
|
const userId = payload.user_id || 'unknown';
|
||||||
|
const userName = payload.user_name || 'unknown';
|
||||||
|
const channelId = payload.channel_id || '';
|
||||||
|
|
||||||
|
if (!text.trim()) {
|
||||||
|
return slackResponse('Please provide an issue title. Usage: `/issue Fix the login bug`');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse priority from text if included
|
||||||
|
let priority = 'P2';
|
||||||
|
let title = text;
|
||||||
|
|
||||||
|
const priorityMatch = text.match(/\b(p[0-3])\b/i);
|
||||||
|
if (priorityMatch) {
|
||||||
|
priority = priorityMatch[1].toUpperCase();
|
||||||
|
title = text.replace(priorityMatch[0], '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect if this should be an agent task
|
||||||
|
const isAgentTask = text.toLowerCase().includes('[agent]') ||
|
||||||
|
text.toLowerCase().includes('--agent');
|
||||||
|
title = title.replace(/\[agent\]/gi, '').replace(/--agent/gi, '').trim();
|
||||||
|
|
||||||
|
// Detect if this is a bug
|
||||||
|
const isBug = text.toLowerCase().includes('[bug]') ||
|
||||||
|
text.toLowerCase().includes('--bug');
|
||||||
|
title = title.replace(/\[bug\]/gi, '').replace(/--bug/gi, '').trim();
|
||||||
|
|
||||||
|
// Build labels
|
||||||
|
const labels = [];
|
||||||
|
if (isBug) {
|
||||||
|
labels.push('bug');
|
||||||
|
} else if (isAgentTask) {
|
||||||
|
labels.push('agent-task', 'automated');
|
||||||
|
} else {
|
||||||
|
labels.push('task');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add priority label
|
||||||
|
switch (priority) {
|
||||||
|
case 'P0': labels.push('p0-now'); break;
|
||||||
|
case 'P1': labels.push('p1-today'); break;
|
||||||
|
case 'P2': labels.push('p2-week'); break;
|
||||||
|
case 'P3': labels.push('p3-backlog'); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build title prefix
|
||||||
|
let fullTitle = title;
|
||||||
|
if (isBug) {
|
||||||
|
fullTitle = `[BUG] ${title}`;
|
||||||
|
} else if (isAgentTask) {
|
||||||
|
fullTitle = `[AGENT] ${title}`;
|
||||||
|
} else {
|
||||||
|
fullTitle = `[TASK] ${title}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the GitHub issue
|
||||||
|
const issue = await createGitHubIssue({
|
||||||
|
title: fullTitle,
|
||||||
|
body: `Created from Slack by @${userName}\n\nChannel: <#${channelId}>`,
|
||||||
|
labels: labels,
|
||||||
|
env: env
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send success response back to Slack
|
||||||
|
return slackResponse(
|
||||||
|
`✅ Issue created: <${issue.html_url}|#${issue.number} ${title}>\n` +
|
||||||
|
`Priority: ${priority} | Labels: ${labels.join(', ')}`
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
return slackResponse(`❌ Failed to create issue: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createGitHubIssue({ title, body, labels, env }) {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.github.com/repos/${env.GITHUB_OWNER}/${env.GITHUB_REPO}/issues`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${env.GITHUB_TOKEN}`,
|
||||||
|
'Accept': 'application/vnd.github.v3+json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'BlackRoad-Slack-Bot'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
labels
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text();
|
||||||
|
throw new Error(`GitHub API error: ${response.status} - ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function slackResponse(text) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
response_type: 'in_channel', // visible to everyone, or 'ephemeral' for private
|
||||||
|
text: text
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment variables needed:
|
||||||
|
*
|
||||||
|
* GITHUB_TOKEN - Personal access token with repo scope
|
||||||
|
* GITHUB_OWNER - Your GitHub username or org
|
||||||
|
* GITHUB_REPO - Repository name
|
||||||
|
* SLACK_SIGNING_SECRET - (optional) For verifying Slack requests
|
||||||
|
*
|
||||||
|
* To verify Slack requests (recommended for production):
|
||||||
|
*
|
||||||
|
* async function verifySlackRequest(request, signingSecret) {
|
||||||
|
* const timestamp = request.headers.get('x-slack-request-timestamp');
|
||||||
|
* const signature = request.headers.get('x-slack-signature');
|
||||||
|
* const body = await request.text();
|
||||||
|
*
|
||||||
|
* const baseString = `v0:${timestamp}:${body}`;
|
||||||
|
* const hmac = crypto.createHmac('sha256', signingSecret);
|
||||||
|
* hmac.update(baseString);
|
||||||
|
* const computed = `v0=${hmac.digest('hex')}`;
|
||||||
|
*
|
||||||
|
* return crypto.timingSafeEqual(
|
||||||
|
* Buffer.from(signature),
|
||||||
|
* Buffer.from(computed)
|
||||||
|
* );
|
||||||
|
* }
|
||||||
|
*/
|
||||||
Reference in New Issue
Block a user