1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-09 13:47:13 +02:00
unleash.unleash/.github/workflows/ai-flag-cleanup-pr.yml
Nuno Góis 2abbf976fe
chore: AI flag cleanup action trigger workflows (#10288)
https://linear.app/unleash/issue/2-3671/adapt-ai-flag-cleanup-action-to-use-our-unleash-bot-gh-app

Adapts our AI flag cleanup action to use our Unleash-Bot GH app. The
main side-effect we're interested in is that this now automatically
triggers the PR checks.

Example PR: https://github.com/Unleash/unleash/pull/10287
2025-07-03 08:48:28 +01:00

300 lines
10 KiB
YAML

name: AI flag cleanup PR
on:
workflow_call:
inputs:
issue-number:
description: "Flag completed issue number"
required: true
type: number
model:
description: "Model to use"
required: true
type: string
api_key_env_name:
description: "The name of the API key environment variable. For example, OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. See more info: https://aider.chat/docs/llms.html"
required: true
type: string
base-branch:
description: "Base branch to create PR against (e.g. main)"
required: false
type: string
default: ${{ github.event.repository.default_branch }}
chat-timeout:
description: "Timeout for flag cleanup, in minutes"
required: false
type: number
default: 10
secrets:
api_key_env_value:
description: "The API key"
required: true
UNLEASH_BOT_APP_ID:
required: true
UNLEASH_BOT_PRIVATE_KEY:
required: true
permissions:
pull-requests: write
contents: write
issues: write
jobs:
create-pull-request:
runs-on: ubuntu-latest
steps:
- name: Generate GitHub App token
id: app_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.UNLEASH_BOT_APP_ID }}
private-key: ${{ secrets.UNLEASH_BOT_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
token: ${{ steps.app_token.outputs.token }}
fetch-depth: 0
persist-credentials: true
- name: Get issue
uses: actions/github-script@v7
id: get_issue
with:
github-token: ${{ steps.app_token.outputs.token }}
script: |
console.log('Fetching issue #${{ inputs.issue-number }}')
const { owner, repo } = context.repo;
const result = await github.rest.issues.get({
owner,
repo,
issue_number: ${{ inputs.issue-number }}
});
return {
title: result.data.title.replace(/"/g, "'").replace(/`/g, '\\`'),
body: result.data.body.replace(/"/g, "'").replace(/`/g, '\\`')
};
- name: Extract flag name
id: extract_flag
run: |
read -r TITLE <<'EOF'
${{ fromJson(steps.get_issue.outputs.result).title }}
EOF
if [[ "$TITLE" =~ Flag[[:space:]]([a-zA-Z0-9_-]+)[[:space:]]marked ]]; then
echo "flag-name=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
else
echo "❌ Could not extract flag name from title: $TITLE" >&2
exit 1
fi
- name: Create a new branch
id: create_branch
uses: actions/github-script@v7
env:
FLAG_NAME: ${{ steps.extract_flag.outputs.flag-name }}
with:
github-token: ${{ steps.app_token.outputs.token }}
result-encoding: string
script: |
const kebab = (s) => s
.toLowerCase().trim()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/^-+|-+$/g, '');
const { owner, repo } = context.repo;
const base = '${{ inputs.base-branch }}';
const baseBranch = await github.rest.repos.getBranch({ owner, repo, branch: base });
const branchName = `chore/unleash-ai-${kebab(process.env.FLAG_NAME)}-flag-cleanup`;
const refFull = `refs/heads/${branchName}`;
const refShort = `heads/${branchName}`;
// remove stale ref if it exists without branch
const existing = await github.rest.git.getRef({ owner, repo, ref: refShort }).catch(() => null);
if (existing) {
try {
await github.rest.repos.getBranch({ owner, repo, branch: branchName });
return branchName;
} catch {
await github.rest.git.deleteRef({ owner, repo, ref: refShort });
}
}
// create new
await github.rest.git.createRef({
owner, repo,
ref: refFull,
sha: baseBranch.data.commit.sha
});
return branchName;
- name: Check out new branch
uses: actions/checkout@v4
with:
ref: ${{ steps.create_branch.outputs.result }}
token: ${{ steps.app_token.outputs.token }}
fetch-depth: 0
persist-credentials: true
- name: Configure Git
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global user.email "194219037+unleash-bot[bot]@users.noreply.github.com"
git config --global user.name "unleash-bot"
- name: Install ripgrep
run: sudo apt-get update && sudo apt-get install -y ripgrep
- name: Find files
id: find_files
run: |
FLAG="${{ steps.extract_flag.outputs.flag-name }}"
mapfile -d '' FILES < <(rg -0 -l "$FLAG" .)
if [[ -z "$FILES" ]]; then
echo "❌ No files found for flag '$FLAG'"
exit 1
fi
printf '%s\0' "${FILES[@]}" > file_list.bin
echo "file_list=./file_list.bin" >> $GITHUB_OUTPUT
- name: Create prompt
id: create_prompt
run: |
ISSUE_BODY="${{ fromJson(steps.get_issue.outputs.result).body }}"
cat <<'EOF' > cleanup_prompt.txt
Based on the issue description below, refactor the codebase to permanently apply the desired outcome for this feature flag (e.g. enable, keep variant, or discard), by removing all conditional checks and dead branches, preserving only the correct code path.
After making the changes, provide a **Markdown summary** of what was changed, written for a developer reviewing the PR. Keep it clear, focused, and readable. Use the exact following format (including start & end separator lines, headings, bullets, emojis):
\`\`\`md
=== AI Flag Cleanup Summary Start ===
## 🧹 AI Flag Cleanup Summary
(Short summary of the changes made)
(Include any details that you think are critical for the reviewer to know, if any, prefixing them with an appropriate emoji)
### 🚮 Removed
- **(Category)**
- (list of removed items)
### 🛠 Kept
- **(Category)**
- (list of kept items)
### 📝 Why
(Your reasoning for the changes made, including any relevant context or decisions that do not belong at the top of the summary.)
=== AI Flag Cleanup Summary End ===
\`\`\`
--- Issue Description ---
$ISSUE_BODY
EOF
echo "prompt-file=cleanup_prompt.txt" >> $GITHUB_OUTPUT
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install build tools & Aider
run: |
python -m pip install --upgrade pip
pip install aider-chat
- name: Run Aider CLI and capture summary
id: run_aider
timeout-minutes: ${{ inputs.chat-timeout }}
env:
GIT_AUTHOR_NAME: 'unleash-bot'
GIT_AUTHOR_EMAIL: '194219037+unleash-bot[bot]@users.noreply.github.com'
GIT_COMMITTER_NAME: 'unleash-bot'
GIT_COMMITTER_EMAIL: '194219037+unleash-bot[bot]@users.noreply.github.com'
${{ inputs.api_key_env_name }}: ${{ secrets.api_key_env_value }}
run: |
mapfile -d '' FILES < <(cat "${{ steps.find_files.outputs.file_list }}")
aider --model "${{ inputs.model }}" \
--yes \
--message-file cleanup_prompt.txt \
--no-attribute-author \
--no-attribute-committer \
--no-attribute-co-authored-by \
"${FILES[@]}" \
| tee aider_output.txt
SUMMARY=$(sed -n '/=== AI Flag Cleanup Summary Start ===/,/=== AI Flag Cleanup Summary End ===/{
/=== AI Flag Cleanup Summary Start ===/d
/=== AI Flag Cleanup Summary End ===/d
p
}' aider_output.txt)
echo "summary<<EOF" >> $GITHUB_OUTPUT
echo "$SUMMARY" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Push commit
run: git push -u origin HEAD
- name: Create Pull Request
uses: actions/github-script@v7
with:
github-token: ${{ steps.app_token.outputs.token }}
result-encoding: string
script: |
const { owner, repo } = context.repo;
const branch = '${{ steps.create_branch.outputs.result }}';
const flag = '${{ steps.extract_flag.outputs.flag-name }}';
const summary = ${{ toJson(steps.run_aider.outputs.summary) }};
const body = [
`This PR cleans up the ${flag} flag. These changes were automatically generated by AI and should be reviewed carefully.`,
'',
`Fixes #${{ inputs.issue-number }}`,
'',
summary
].join('\n');
const { data: prs } = await github.rest.pulls.list({ owner, repo, state: 'open', per_page: 100 });
const existing = prs.find(p => p.head.ref === branch);
if (existing) {
await github.rest.pulls.update({
owner,
repo,
pull_number: existing.number,
body
});
console.log(`Updated PR #${existing.number}: ${existing.html_url}`);
return existing;
}
const { data: pr } = await github.rest.pulls.create({
owner,
repo,
title: `chore(AI): ${flag} flag cleanup`,
head: branch,
base: '${{ inputs.base-branch }}',
body
});
await github.rest.issues.addLabels({
owner,
repo,
issue_number: pr.number,
labels: ['unleash-ai-flag-cleanup']
});
console.log(`Created PR #${pr.number}: ${pr.html_url}`);
return pr;