mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
chore: AI flag cleanup extended experiment (#10254)
Experimenting more with AI flag cleanup.
This commit is contained in:
parent
661fd6febf
commit
6503deea9b
258
.github/workflows/ai-flag-cleanup-pr.yml
vendored
Normal file
258
.github/workflows/ai-flag-cleanup-pr.yml
vendored
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: write
|
||||||
|
issues: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-pull-request:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref }}
|
||||||
|
|
||||||
|
- name: Create a new branch
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
id: create_branch
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const kebabCase = (str) => {
|
||||||
|
return str
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/[^\w\s-]/g, '') // Remove invalid characters
|
||||||
|
.replace(/\s+/g, '-') // Replace spaces with dashes
|
||||||
|
.replace(/^-+|-+$/g, ''); // Remove leading/trailing dashes
|
||||||
|
};
|
||||||
|
|
||||||
|
const fixBranchUrl = (url) => url
|
||||||
|
.replace(/\/git\/commits/, '/commit')
|
||||||
|
.replace(/api.github.com\/repos/, 'github.com');
|
||||||
|
|
||||||
|
// New branch should be based on the base-branch, so we need to get its SHA
|
||||||
|
const baseBranch = await github.rest.repos.getBranch({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
branch: '${{ inputs.base-branch }}'
|
||||||
|
});
|
||||||
|
|
||||||
|
const { repo, owner } = context.repo;
|
||||||
|
const branchName = 'feature/aider-' + kebabCase(context.payload.issue.title);
|
||||||
|
const refName = `refs/heads/${branchName}`
|
||||||
|
const refShortName = `heads/${branchName}`
|
||||||
|
|
||||||
|
// Get existing ref if exists
|
||||||
|
const existingRef = await github.rest.git.getRef({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
ref: refShortName
|
||||||
|
}).catch(() => null);
|
||||||
|
|
||||||
|
if (existingRef) {
|
||||||
|
try {
|
||||||
|
// If there's a branch for this ref, return the ref
|
||||||
|
await github.rest.repos.getBranch({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
branch: branchName
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Branch ${branchName} already exists with SHA ${existingRef.data.object.sha}`);
|
||||||
|
console.log(`Branch URL: ${fixBranchUrl(existingRef.data.object.url)}`);
|
||||||
|
|
||||||
|
return { ref: existingRef.data.ref }
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
// State recovery: If there's a ref but no branch, delete the ref and create a new branch
|
||||||
|
// This can happen if the branch was deleted manually. The ref will still exist.
|
||||||
|
console.log(`Branch ${branchName} doesn't exist, deleting ref ${refShortName}`);
|
||||||
|
await github.rest.git.deleteRef({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
ref: refShortName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create branch
|
||||||
|
const result = await github.rest.git.createRef({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
ref: refName,
|
||||||
|
sha: baseBranch.data.commit.sha
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Created branch ${branchName} with SHA ${result.data.object.sha}`);
|
||||||
|
console.log(`Branch URL: ${fixBranchUrl(result.data.object.url)}`);
|
||||||
|
|
||||||
|
return { ref: result.data.ref }
|
||||||
|
|
||||||
|
- name: Get issue
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
id: get_issue
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
console.log('Fetching issue #${{ inputs.issue-number }}')
|
||||||
|
const { repo, owner } = context.repo;
|
||||||
|
const result = await github.rest.issues.get({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: ${{ inputs.issue-number }}
|
||||||
|
});
|
||||||
|
console.log(`Fetched issue #${result.data.number}: ${result.data.title}`)
|
||||||
|
|
||||||
|
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: |
|
||||||
|
TITLE="${{ fromJson(steps.get_issue.outputs.result).title }}"
|
||||||
|
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"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install ripgrep
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y ripgrep
|
||||||
|
|
||||||
|
- name: Find files using the flag
|
||||||
|
id: find_files
|
||||||
|
run: |
|
||||||
|
FLAG="${{ steps.extract_flag.outputs.flag-name }}"
|
||||||
|
FILES=$(rg -0 -l "$FLAG" . | xargs -0 -I{} printf '"%s" ' "{}")
|
||||||
|
|
||||||
|
if [[ -z "$FILES" ]]; then
|
||||||
|
echo "❌ No files found using flag '$FLAG'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "file_args=$FILES" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Create prompt
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
id: create_prompt
|
||||||
|
with:
|
||||||
|
result-encoding: string
|
||||||
|
script: |
|
||||||
|
const body = `${{ fromJson(steps.get_issue.outputs.result).body }}`;
|
||||||
|
|
||||||
|
return `Apply all necessary changes to clean up this feature flag based on the issue description below.
|
||||||
|
|
||||||
|
After making the changes, provide a Markdown summary of what was changed, written for a developer reviewing the PR. These changes should be under a "AI Flag Cleanup Summary" section.
|
||||||
|
|
||||||
|
Explain:
|
||||||
|
- What was removed
|
||||||
|
- What was kept
|
||||||
|
- Why these changes were made
|
||||||
|
|
||||||
|
Write a natural summary in proper Markdown. Keep it clear, focused, and readable.
|
||||||
|
|
||||||
|
--- Issue Description ---
|
||||||
|
${body}`;
|
||||||
|
|
||||||
|
- name: Apply cleanup with Aider
|
||||||
|
uses: mirrajabi/aider-github-action@v1.1.0
|
||||||
|
timeout-minutes: ${{ inputs.chat-timeout }}
|
||||||
|
with:
|
||||||
|
branch: ${{ fromJson(steps.create_branch.outputs.result).ref }}
|
||||||
|
model: ${{ inputs.model }}
|
||||||
|
aider_args: '--yes ${{ steps.find_files.outputs.file_args }} --message "${{ steps.create_prompt.outputs.result }}"'
|
||||||
|
api_key_env_name: ${{ inputs.api_key_env_name }}
|
||||||
|
api_key_env_value: ${{ secrets.api_key_env_value }}
|
||||||
|
|
||||||
|
- name: Extract summary from Aider output
|
||||||
|
id: extract_summary
|
||||||
|
run: |
|
||||||
|
SUMMARY=$(awk '/AI Flag Cleanup Summary/ {found=1} found' .aider.chat.history.md)
|
||||||
|
|
||||||
|
echo "summary<<EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "$SUMMARY" >> $GITHUB_OUTPUT
|
||||||
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { repo, owner } = context.repo;
|
||||||
|
const branchRef = '${{ fromJson(steps.create_branch.outputs.result).ref }}'
|
||||||
|
const flagName = '${{ steps.extract_flag.outputs.flag-name }}';
|
||||||
|
|
||||||
|
// If PR already exists, return it
|
||||||
|
const pulls = await github.rest.pulls.list({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
state: 'open',
|
||||||
|
per_page: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingPR = pulls.data.find((pr) => pr.head.ref === branchRef);
|
||||||
|
if (existingPR) {
|
||||||
|
console.log(`PR #${existingPR.number} already exists: ${existingPR.html_url}`);
|
||||||
|
return existingPR;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPR = await github.rest.pulls.create({
|
||||||
|
title: `[AI] ${flagName} flag cleanup`,
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
head: branchRef,
|
||||||
|
base: 'refs/heads/${{ inputs.base-branch }}',
|
||||||
|
body: [
|
||||||
|
`This PR cleans up the ${flagName} flag. These changes were automatically generated by AI and should be reviewed carefully.`,
|
||||||
|
'',
|
||||||
|
`Fixes #${{ inputs.issue-number }}`,
|
||||||
|
'',
|
||||||
|
`${{ steps.extract_summary.outputs.summary }}`
|
||||||
|
].join('\n')
|
||||||
|
});
|
||||||
|
github.rest.issues.addLabels({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: newPR.data.number,
|
||||||
|
labels: ['unleash-ai-flag-cleanup']
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Created PR #${newPR.data.number}: ${newPR.data.html_url}`);
|
||||||
|
- name: Upload aider chat history
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: aider-chat-output
|
||||||
|
path: ".aider.chat.history.md"
|
24
.github/workflows/ai-flag-cleanup.yml
vendored
24
.github/workflows/ai-flag-cleanup.yml
vendored
@ -1,22 +1,24 @@
|
|||||||
name: AI flag cleanup
|
name: AI flag cleanup
|
||||||
|
|
||||||
on:
|
on:
|
||||||
issues:
|
issues:
|
||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
workflow_dispatch:
|
||||||
permissions:
|
inputs:
|
||||||
pull-requests: write
|
issue-number:
|
||||||
contents: write
|
description: 'Flag completed issue number'
|
||||||
issues: read
|
required: true
|
||||||
|
type: number
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
flag-cleanup:
|
flag-cleanup:
|
||||||
uses: mirrajabi/aider-github-workflows/.github/workflows/aider-issue-to-pr.yml@v1.0.0
|
if: |
|
||||||
if: github.event.label.name == 'unleash-flag-completed'
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event_name == 'issues' && github.event.label.name == 'unleash-flag-completed')
|
||||||
|
uses: ./.github/workflows/ai-flag-cleanup-pr.yml
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number || inputs.issue-number }}
|
||||||
base-branch: ${{ github.event.repository.default_branch }}
|
|
||||||
chat-timeout: 10
|
|
||||||
api_key_env_name: GEMINI_API_KEY
|
|
||||||
model: gemini
|
model: gemini
|
||||||
|
api_key_env_name: GEMINI_API_KEY
|
||||||
secrets:
|
secrets:
|
||||||
api_key_env_value: ${{ secrets.GEMINI_API_KEY }}
|
api_key_env_value: ${{ secrets.GEMINI_API_KEY }}
|
||||||
|
Loading…
Reference in New Issue
Block a user