Skip to content

Unit ownership should use SQLite, not JSON file — read-modify-write race #16

Unit ownership should use SQLite, not JSON file — read-modify-write race

Unit ownership should use SQLite, not JSON file — read-modify-write race #16

Workflow file for this run

name: AI Triage
on:
issues:
types: [opened]
pull_request_target:
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: 1024,
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;
}
let result;
try {
result = JSON.parse(jsonMatch[0]);
} catch (e) {
core.setFailed(`JSON parse error: ${e.message}\nRaw text: ${text}`);
return;
}
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}`);
}