feat: AI-powered issue and PR triage via Claude Haiku (#1510)
Adds a GitHub Actions workflow that automatically triages new issues and PRs using Claude Haiku 4.5. Classifies with type and priority labels, and flags items that violate VISION.md or CONTRIBUTING.md guidelines with a `needs-review` label and explanatory comment. No auto-closing — maintainer makes all final decisions. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aa9e1cfea9
commit
4c081fa556
1 changed files with 191 additions and 0 deletions
191
.github/workflows/ai-triage.yml
vendored
Normal file
191
.github/workflows/ai-triage.yml
vendored
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
name: AI Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
VISION.md
|
||||
CONTRIBUTING.md
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Triage with Claude
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const vision = fs.readFileSync('VISION.md', 'utf8');
|
||||
const contributing = fs.readFileSync('CONTRIBUTING.md', 'utf8');
|
||||
|
||||
const isIssue = !!context.payload.issue;
|
||||
const item = context.payload.issue || context.payload.pull_request;
|
||||
const number = item.number;
|
||||
const title = item.title;
|
||||
const body = item.body || '';
|
||||
const type = isIssue ? 'issue' : 'pull_request';
|
||||
const existingLabels = (item.labels || []).map(l => l.name);
|
||||
|
||||
const prompt = `You are a GitHub issue/PR triage bot for the GSD-2 project. Your job is to:
|
||||
1. Classify the ${type} with appropriate labels
|
||||
2. Detect if it violates project guidelines
|
||||
|
||||
<vision>
|
||||
${vision}
|
||||
</vision>
|
||||
|
||||
<contributing>
|
||||
${contributing}
|
||||
</contributing>
|
||||
|
||||
<${type}>
|
||||
Title: ${title}
|
||||
Body: ${body}
|
||||
Existing labels: ${existingLabels.join(', ') || 'none'}
|
||||
</${type}>
|
||||
|
||||
Available labels for classification (pick 1-3 that fit):
|
||||
- bug: Something isn't working
|
||||
- enhancement: New feature or request
|
||||
- documentation: Improvements or additions to documentation
|
||||
- performance: Performance improvement
|
||||
- refactor: Code restructuring without behavior change
|
||||
- tech-debt: Technical debt reduction
|
||||
- question: Further information is requested
|
||||
- good first issue: Good for newcomers
|
||||
- needs-info: Waiting for reporter information
|
||||
|
||||
Available priority labels (pick exactly 1 if you can assess priority):
|
||||
- High Priority
|
||||
- Medium Priority
|
||||
- Low Priority
|
||||
|
||||
Respond in this exact JSON format:
|
||||
{
|
||||
"labels": ["label1", "label2"],
|
||||
"aligned": true,
|
||||
"violation_type": null,
|
||||
"explanation": null
|
||||
}
|
||||
|
||||
Set "aligned" to false if the ${type} clearly violates project guidelines. Violation types:
|
||||
- "enterprise-patterns": Enterprise patterns (DI containers, abstract factories, etc.)
|
||||
- "framework-swap": Rewriting working code in a different framework without measurable improvement
|
||||
- "cosmetic-refactor": Pure formatting/renaming churn with no user value
|
||||
- "complexity-without-value": Adds abstraction/indirection without user-visible improvement
|
||||
- "heavy-orchestration": Duplicates what agent infrastructure already provides
|
||||
- "missing-info": Issue is too vague to act on (no repro steps, no context)
|
||||
- "off-topic": Not related to GSD-2 at all
|
||||
- "security-in-public": Appears to report a security vulnerability publicly
|
||||
|
||||
If aligned is false, provide a brief, polite explanation (2-3 sentences) of why this was flagged.
|
||||
|
||||
Be generous in your assessment — only flag clear violations. Ambiguous cases should be marked as aligned.
|
||||
Do NOT flag issues/PRs that are legitimately reporting bugs or requesting features, even if they could be better written.`;
|
||||
|
||||
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-api-key': process.env.ANTHROPIC_API_KEY,
|
||||
'content-type': 'application/json',
|
||||
'anthropic-version': '2023-06-01'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'claude-haiku-4-5-20251001',
|
||||
max_tokens: 300,
|
||||
messages: [{ role: 'user', content: prompt }]
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.text();
|
||||
core.setFailed(`Anthropic API error: ${response.status} ${err}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const text = data.content[0].text;
|
||||
|
||||
// Extract JSON from response (handle markdown code blocks)
|
||||
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
||||
if (!jsonMatch) {
|
||||
core.setFailed(`Could not parse Claude response: ${text}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = JSON.parse(jsonMatch[0]);
|
||||
core.info(`Triage result: ${JSON.stringify(result, null, 2)}`);
|
||||
|
||||
// Apply labels
|
||||
const labelsToAdd = result.labels.filter(l => !existingLabels.includes(l));
|
||||
if (labelsToAdd.length > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: number,
|
||||
labels: labelsToAdd
|
||||
});
|
||||
core.info(`Added labels: ${labelsToAdd.join(', ')}`);
|
||||
}
|
||||
|
||||
// Flag misaligned issues/PRs
|
||||
if (!result.aligned) {
|
||||
// Add needs-review label
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: number,
|
||||
labels: ['needs-review']
|
||||
});
|
||||
|
||||
const violationNames = {
|
||||
'enterprise-patterns': 'Enterprise patterns',
|
||||
'framework-swap': 'Framework swap',
|
||||
'cosmetic-refactor': 'Cosmetic refactor',
|
||||
'complexity-without-value': 'Complexity without user value',
|
||||
'heavy-orchestration': 'Heavy orchestration layer',
|
||||
'missing-info': 'Missing information',
|
||||
'off-topic': 'Off-topic',
|
||||
'security-in-public': 'Security report in public'
|
||||
};
|
||||
|
||||
const securityNote = result.violation_type === 'security-in-public'
|
||||
? `\n\n**If this is a security vulnerability, please delete this ${type} and use [GitHub\'s private vulnerability reporting](https://github.com/gsd-build/GSD-2/security/advisories/new) instead.** See [CONTRIBUTING.md](https://github.com/gsd-build/GSD-2/blob/main/CONTRIBUTING.md#security) for details.`
|
||||
: '';
|
||||
|
||||
const comment = `👋 Thanks for opening this ${type}!
|
||||
|
||||
This was automatically flagged for maintainer review.
|
||||
|
||||
**Flag:** ${violationNames[result.violation_type] || result.violation_type}
|
||||
|
||||
${result.explanation}
|
||||
|
||||
Please review our [VISION.md](https://github.com/gsd-build/GSD-2/blob/main/VISION.md) and [CONTRIBUTING.md](https://github.com/gsd-build/GSD-2/blob/main/CONTRIBUTING.md) for project guidelines.${securityNote}
|
||||
|
||||
A maintainer will review this shortly. If you believe this was flagged in error, no action is needed — we'll take a look.
|
||||
|
||||
---
|
||||
*This is an automated triage. The maintainers make all final decisions.*`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: number,
|
||||
body: comment
|
||||
});
|
||||
core.info(`Flagged as misaligned: ${result.violation_type}`);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue