singularity-forge/.github/workflows/ai-triage.yml
ace-pm 9d739dfa5d Rename GSD→SF: complete rebrand from fork origin
- All gsdDir/gsdRoot/gsdHome → sfDir/sfRootDir/sfHome
- GSDWorkspace* → SFWorkspace* interfaces
- bootstrapGsdProject → bootstrapProject
- runGSDDoctor → runSFDoctor
- GsdClient → SfClient, gsd-client.ts → sf-client.ts
- .gsd/ → .sf/ in all tests, docs, docker, native, vscode
- Auto-migration: headless detects .gsd/ → renames to .sf/
- Deleted gsd-phase-state.ts backward-compat re-export
- Renamed bin/gsd-from-source → bin/sf-from-source
- Updated mintlify docs, github workflows, docker configs
2026-04-15 18:33:47 +02:00

203 lines
8.2 KiB
YAML

name: AI Triage
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
permissions:
issues: write
pull-requests: write
jobs:
triage:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v6
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 SF 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 SF 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.`;
if (!process.env.ANTHROPIC_API_KEY) {
core.warning('Skipping AI triage because ANTHROPIC_API_KEY is not configured.');
return;
}
let result;
try {
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 }]
}),
signal: AbortSignal.timeout(20000)
});
if (!response.ok) {
const err = await response.text();
core.warning(`Skipping AI triage after 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.warning(`Skipping AI triage because the model response was not parseable JSON: ${text}`);
return;
}
result = JSON.parse(jsonMatch[0]);
} catch (e) {
core.warning(`Skipping AI triage after unexpected failure: ${e.message}`);
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/sf-build/SF/security/advisories/new) instead.** See [CONTRIBUTING.md](https://github.com/sf-build/SF/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/sf-build/SF/blob/main/VISION.md) and [CONTRIBUTING.md](https://github.com/sf-build/SF/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}`);
}