Files
hassio-addons/.github/workflows/ai-triage.yml
dependabot[bot] 5552f34bc1 Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 00:42:50 +00:00

193 lines
6.5 KiB
YAML

name: AI triage (comment + optional PR)
on:
issues:
types: [opened]
permissions:
contents: write
pull-requests: write
issues: write
jobs:
ai_first_reply:
runs-on: ubuntu-latest
steps:
- name: Compose and post AI reply
uses: actions/github-script@v7
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
with:
script: |
const issue = context.payload.issue;
const { owner, repo } = context.repo;
// Optionally pull README for a bit of repo context
let readmeText = '';
try {
const readme = await github.rest.repos.getReadme({ owner, repo });
readmeText = Buffer.from(readme.data.content, 'base64').toString('utf8');
if (readmeText.length > 18000) readmeText = readmeText.slice(0, 18000) + "\n...[truncated]...";
} catch {}
const system = `
You are an open-source triage assistant.
Write a concise, actionable first reply for a new GitHub issue.
Ask for missing repro details; avoid inventing repo facts.
Propose next steps and link to repo files only when certain.
`.trim();
const user = `
Repository: ${owner}/${repo}
Issue #${issue.number}: ${issue.title}
Issue body:
${issue.body || '(no body provided)'}
README excerpt:
${readmeText || '(none)'}
`.trim();
const res = await fetch('https://api.openai.com/v1/responses', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4o-mini',
input: [
{ role: 'system', content: system },
{ role: 'user', content: user }
],
}),
});
if (!res.ok) {
const txt = await res.text();
await github.rest.issues.createComment({
owner, repo, issue_number: issue.number,
body: `⚠️ AI reply failed (${res.status}).\n\n\`\`\`\n${txt}\n\`\`\``
});
return;
}
const data = await res.json();
const text = data.output_text
|| data?.choices?.[0]?.message?.content
|| 'Thanks for opening this issue — we will take a look!';
await github.rest.issues.createComment({
owner, repo, issue_number: issue.number, body: text
});
ai_autofix_pr:
runs-on: ubuntu-latest
needs: ai_first_reply
if: ${{ contains(github.event.issue.labels.*.name, 'auto-pr') }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Generate patch with OpenAI (unified diff)
id: genpatch
uses: actions/github-script@v7
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
with:
result-encoding: string
script: |
const { owner, repo } = context.repo;
const issue = context.payload.issue;
let readmeText = '';
try {
const readme = await github.rest.repos.getReadme({ owner, repo });
readmeText = Buffer.from(readme.data.content, 'base64').toString('utf8');
if (readmeText.length > 12000) readmeText = readmeText.slice(0, 12000) + "\n...[truncated]...";
} catch {}
const system = `
You are a code-change generator.
Output ONLY a git unified diff (git apply compatible). No prose, no backticks.
Keep changes minimal and safe; if unsure, output an empty diff.
`.trim();
const user = `
Repo: ${owner}/${repo}
Issue #${issue.number}: ${issue.title}
Issue body:
${issue.body || '(no body)'}
README excerpt:
${readmeText || '(none)'}
`.trim();
const res = await fetch('https://api.openai.com/v1/responses', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4o-mini',
input: [
{ role: 'system', content: system },
{ role: 'user', content: user }
],
}),
});
if (!res.ok) {
const txt = await res.text();
core.setFailed(`OpenAI error ${res.status}: ${txt}`);
} else {
const data = await res.json();
const patch = (data.output_text || data?.choices?.[0]?.message?.content || '').trim();
return patch;
}
- name: Apply patch (if any)
id: apply
shell: bash
run: |
set -euo pipefail
branch="ai/autofix-${{ github.event.issue.number }}"
patch="${{ steps.genpatch.outputs.result }}"
if [ -z "$patch" ]; then
echo "No patch produced; skipping PR."
echo "skip_pr=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "$patch" > ai.patch
git config user.name "ai-triage-bot"
git config user.email "ai-triage-bot@users.noreply.github.com"
git checkout -b "$branch"
git apply --whitespace=fix ai.patch
git add -A
git commit -m "AI autofix for #${{ github.event.issue.number }}"
git push -u origin "$branch"
echo "skip_pr=false" >> "$GITHUB_OUTPUT"
- name: Open PR
if: steps.apply.outputs.skip_pr == 'false'
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const head = `ai/autofix-${{ github.event.issue.number }}`;
await github.rest.pulls.create({
owner, repo,
head,
base: 'main',
title: `AI autofix for #${{ github.event.issue.number }}`,
body: `This PR was generated automatically from issue #${{ github.event.issue.number }}.
> Label \`auto-pr\` triggered patch creation. Please review carefully.`,
maintainer_can_modify: true
});