Auto Reset Community Backlog #13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Auto Reset Community Backlog | |
| on: | |
| workflow_call: | |
| inputs: | |
| minimum_available: | |
| description: 'Reset a backlog when available items are at or below this count' | |
| required: false | |
| default: '5' | |
| type: string | |
| full_reset: | |
| description: 'Reset every backlog item regardless of current availability' | |
| required: false | |
| default: false | |
| type: boolean | |
| dry_run: | |
| description: 'Do not write changes; only report what would be reset' | |
| required: false | |
| default: false | |
| type: boolean | |
| workflow_dispatch: | |
| inputs: | |
| minimum_available: | |
| description: 'Reset a backlog when available items are at or below this count' | |
| required: false | |
| default: '5' | |
| type: string | |
| full_reset: | |
| description: 'Reset every backlog item regardless of current availability' | |
| required: false | |
| type: boolean | |
| dry_run: | |
| description: 'Do not write changes; only report what would be reset' | |
| required: false | |
| type: boolean | |
| permissions: | |
| actions: read | |
| contents: write | |
| pull-requests: write | |
| concurrency: | |
| group: auto-reset-community-backlog | |
| cancel-in-progress: true | |
| jobs: | |
| reset-backlogs: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| if: github.repository == 'lingdojo/kana-dojo' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.AUTOMATION_PR_TOKEN }} | |
| - name: Reset low-availability backlogs | |
| id: reset | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.AUTOMATION_PR_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const backlogDir = 'community/backlog'; | |
| const minimumAvailable = Number(process.env.MINIMUM_AVAILABLE || 5); | |
| const dryRun = String(process.env.DRY_RUN || 'false') === 'true'; | |
| const fullReset = String(process.env.FULL_RESET || 'false') === 'true'; | |
| const backlogFiles = [ | |
| ['theme', 'theme-backlog.json'], | |
| ['fact', 'facts-backlog.json'], | |
| ['proverb', 'proverbs-backlog.json'], | |
| ['haiku', 'haiku-backlog.json'], | |
| ['trivia', 'trivia-backlog.json'], | |
| ['grammar', 'grammar-backlog.json'], | |
| ['animeQuote', 'anime-quotes-backlog.json'], | |
| ['videoGameQuote', 'video-game-quotes-backlog.json'], | |
| ['idiom', 'idioms-backlog.json'], | |
| ['regionalDialect', 'regional-dialects-backlog.json'], | |
| ['falseFriend', 'false-friends-backlog.json'], | |
| ['culturalEtiquette', 'cultural-etiquette-backlog.json'], | |
| ['exampleSentence', 'example-sentences-backlog.json'], | |
| ['commonMistake', 'common-mistakes-backlog.json'], | |
| ['wallpaperUrl', 'wallpaper-urls-backlog.json'], | |
| ['communityNote', 'community-notes-backlog.json'], | |
| ]; | |
| if (!Number.isInteger(minimumAvailable) || minimumAvailable < 0) { | |
| throw new Error(`Invalid minimum_available input: ${context.payload.inputs && context.payload.inputs.minimum_available}`); | |
| } | |
| const changedFiles = []; | |
| let resetCount = 0; | |
| let filesConsidered = 0; | |
| for (const [type, file] of backlogFiles) { | |
| const filePath = path.join(backlogDir, file); | |
| const items = JSON.parse(fs.readFileSync(filePath, 'utf8')); | |
| const available = items.filter(function(item) { | |
| return !item.issued && !item.completed; | |
| }).length; | |
| filesConsidered += 1; | |
| if (!fullReset && available > minimumAvailable) { | |
| console.log(`Skipping ${type}: available=${available}`); | |
| continue; | |
| } | |
| const resetItems = items.map(function(item) { | |
| const next = { ...item, issued: false, completed: false }; | |
| delete next.completedBy; | |
| delete next.completedPR; | |
| return next; | |
| }); | |
| console.log(`Resetting ${type}: available=${available}, total=${items.length}`); | |
| changedFiles.push(filePath); | |
| resetCount += 1; | |
| if (!dryRun) { | |
| fs.writeFileSync(filePath, JSON.stringify(resetItems, null, 2) + '\n'); | |
| } | |
| } | |
| console.log(`Backlog auto-reset summary: changed=${resetCount}, considered=${filesConsidered}, minimum_available=${minimumAvailable}, full_reset=${fullReset}, dry_run=${dryRun}`); | |
| core.setOutput('changed_files', changedFiles.join(' ')); | |
| core.setOutput('changed_count', String(resetCount)); | |
| core.setOutput('needs_commit', !dryRun && resetCount > 0 ? 'true' : 'false'); | |
| core.setOutput('mode', fullReset ? 'full_reset' : 'threshold_reset'); | |
| env: | |
| MINIMUM_AVAILABLE: ${{ inputs.minimum_available || github.event.inputs.minimum_available || '5' }} | |
| FULL_RESET: ${{ inputs.full_reset || github.event.inputs.full_reset || 'false' }} | |
| DRY_RUN: ${{ inputs.dry_run || github.event.inputs.dry_run || 'false' }} | |
| - name: Commit backlog resets directly to main | |
| if: steps.reset.outputs.needs_commit == 'true' | |
| run: | | |
| git config user.name "てんとう虫" | |
| git config user.email "reservecrate@gmail.com" | |
| git add ${{ steps.reset.outputs.changed_files }} | |
| if git diff --cached --quiet; then | |
| echo "No backlog reset changes to commit" | |
| exit 0 | |
| fi | |
| mode="${{ steps.reset.outputs.mode }}" | |
| if [ "$mode" = "full_reset" ]; then | |
| commit_msg="chore(automation): full reset community backlog" | |
| else | |
| commit_msg="chore(automation): auto reset community backlog" | |
| fi | |
| max_attempts=5 | |
| attempt=1 | |
| while [ "$attempt" -le "$max_attempts" ]; do | |
| git commit -m "$commit_msg" | |
| if git pull --rebase origin main && git push origin HEAD:main; then | |
| echo "Backlog reset push succeeded on attempt $attempt" | |
| exit 0 | |
| fi | |
| echo "Backlog reset push attempt $attempt failed; retrying..." | |
| git rebase --abort || true | |
| git fetch origin main | |
| git reset --hard origin/main | |
| git add ${{ steps.reset.outputs.changed_files }} | |
| if git diff --cached --quiet; then | |
| echo "No backlog reset changes remain after sync" | |
| exit 0 | |
| fi | |
| attempt=$((attempt + 1)) | |
| done | |
| echo "Failed to push backlog reset updates after $max_attempts attempts" | |
| exit 1 |