- 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
203 lines
8.2 KiB
YAML
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}`);
|
|
}
|