Issue Milestone Hygiene #6700
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: Issue Milestone Hygiene | |
| on: | |
| issues: | |
| types: | |
| - opened | |
| - edited | |
| - reopened | |
| - labeled | |
| - unlabeled | |
| - milestoned | |
| - demilestoned | |
| schedule: | |
| - cron: '25 * * * *' | |
| workflow_dispatch: | |
| inputs: | |
| apply_default_milestone: | |
| description: 'How to handle default milestone assignment' | |
| required: false | |
| default: inherit | |
| type: choice | |
| options: | |
| - inherit | |
| - apply | |
| - disable | |
| create_default_milestone: | |
| description: 'How to handle default milestone creation' | |
| required: false | |
| default: inherit | |
| type: choice | |
| options: | |
| - inherit | |
| - apply | |
| - disable | |
| default_milestone: | |
| description: 'Default milestone title override' | |
| required: false | |
| type: string | |
| default_milestone_due_on: | |
| description: 'ISO-8601 due date when creating default milestone' | |
| required: false | |
| type: string | |
| warn_only: | |
| description: 'Warn only (do not fail)' | |
| required: false | |
| default: false | |
| type: boolean | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.issue.number || github.ref || github.run_id }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| issues: write | |
| jobs: | |
| issue-milestone-hygiene: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Evaluate milestone hygiene policy | |
| shell: bash | |
| env: | |
| REPO_SLUG: ${{ github.repository }} | |
| GITHUB_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN || github.token }} | |
| GH_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN || github.token }} | |
| APPLY_INPUT: ${{ inputs.apply_default_milestone }} | |
| CREATE_INPUT: ${{ inputs.create_default_milestone }} | |
| DEFAULT_INPUT: ${{ inputs.default_milestone }} | |
| DUE_INPUT: ${{ inputs.default_milestone_due_on }} | |
| WARN_INPUT: ${{ inputs.warn_only }} | |
| APPLY_VAR: ${{ vars.MILESTONE_HYGIENE_APPLY_DEFAULT || '' }} | |
| CREATE_VAR: ${{ vars.MILESTONE_HYGIENE_CREATE_DEFAULT || '' }} | |
| DEFAULT_VAR: ${{ vars.MILESTONE_HYGIENE_DEFAULT_MILESTONE || '' }} | |
| DUE_VAR: ${{ vars.MILESTONE_HYGIENE_DEFAULT_MILESTONE_DUE_ON || '' }} | |
| WARN_VAR: ${{ vars.MILESTONE_HYGIENE_WARN_ONLY || '0' }} | |
| run: | | |
| set -euo pipefail | |
| args=( | |
| "tools/npm/run-script.mjs" | |
| "priority:milestone:hygiene" | |
| "--" | |
| "--repo" "$REPO_SLUG" | |
| "--report" "tests/results/_agent/issue/milestone-hygiene-report.json" | |
| ) | |
| warn_effective="${WARN_INPUT:-}" | |
| if [[ -z "$warn_effective" || "$warn_effective" == "false" ]]; then | |
| warn_effective="${WARN_VAR:-0}" | |
| fi | |
| warn_effective="$(echo "$warn_effective" | tr '[:upper:]' '[:lower:]')" | |
| if [[ "$warn_effective" == "1" || "$warn_effective" == "true" || "$warn_effective" == "yes" || "$warn_effective" == "on" ]]; then | |
| args+=("--warn-only") | |
| fi | |
| default_effective="${DEFAULT_INPUT:-}" | |
| if [[ -z "$default_effective" ]]; then | |
| default_effective="${DEFAULT_VAR:-}" | |
| fi | |
| if [[ -n "$default_effective" ]]; then | |
| args+=("--default-milestone" "$default_effective") | |
| fi | |
| due_effective="${DUE_INPUT:-}" | |
| if [[ -z "$due_effective" ]]; then | |
| due_effective="${DUE_VAR:-}" | |
| fi | |
| if [[ -n "$due_effective" ]]; then | |
| args+=("--default-milestone-due-on" "$due_effective") | |
| fi | |
| apply_input_effective="$(echo "${APPLY_INPUT:-}" | tr '[:upper:]' '[:lower:]')" | |
| apply_var_effective="$(echo "${APPLY_VAR:-}" | tr '[:upper:]' '[:lower:]')" | |
| if [[ "$apply_input_effective" == "apply" || "$apply_input_effective" == "1" || "$apply_input_effective" == "true" || "$apply_input_effective" == "yes" || "$apply_input_effective" == "on" ]]; then | |
| args+=("--apply-default-milestone") | |
| elif [[ "$apply_input_effective" == "disable" || "$apply_input_effective" == "0" || "$apply_input_effective" == "false" || "$apply_input_effective" == "no" || "$apply_input_effective" == "off" ]]; then | |
| args+=("--no-apply-default-milestone") | |
| elif [[ "$apply_var_effective" == "1" || "$apply_var_effective" == "true" || "$apply_var_effective" == "yes" || "$apply_var_effective" == "on" ]]; then | |
| args+=("--apply-default-milestone") | |
| elif [[ "$apply_var_effective" == "0" || "$apply_var_effective" == "false" || "$apply_var_effective" == "no" || "$apply_var_effective" == "off" ]]; then | |
| args+=("--no-apply-default-milestone") | |
| fi | |
| create_input_effective="$(echo "${CREATE_INPUT:-}" | tr '[:upper:]' '[:lower:]')" | |
| create_var_effective="$(echo "${CREATE_VAR:-}" | tr '[:upper:]' '[:lower:]')" | |
| if [[ "$create_input_effective" == "apply" || "$create_input_effective" == "1" || "$create_input_effective" == "true" || "$create_input_effective" == "yes" || "$create_input_effective" == "on" ]]; then | |
| args+=("--create-default-milestone") | |
| elif [[ "$create_input_effective" == "disable" || "$create_input_effective" == "0" || "$create_input_effective" == "false" || "$create_input_effective" == "no" || "$create_input_effective" == "off" ]]; then | |
| args+=("--no-create-default-milestone") | |
| elif [[ "$create_var_effective" == "1" || "$create_var_effective" == "true" || "$create_var_effective" == "yes" || "$create_var_effective" == "on" ]]; then | |
| args+=("--create-default-milestone") | |
| elif [[ "$create_var_effective" == "0" || "$create_var_effective" == "false" || "$create_var_effective" == "no" || "$create_var_effective" == "off" ]]; then | |
| args+=("--no-create-default-milestone") | |
| fi | |
| node "${args[@]}" | |
| - name: Append summary | |
| if: always() | |
| shell: bash | |
| run: | | |
| node <<'NODE' | |
| const fs = require('fs'); | |
| const reportPath = 'tests/results/_agent/issue/milestone-hygiene-report.json'; | |
| if (!fs.existsSync(reportPath)) { | |
| console.log('milestone hygiene report missing'); | |
| process.exit(0); | |
| } | |
| const report = JSON.parse(fs.readFileSync(reportPath, 'utf8')); | |
| const remaining = report.summary?.remainingViolationCount ?? 0; | |
| const lines = [ | |
| '## Issue Milestone Hygiene', | |
| `- execution status: \`${report.execution?.status ?? 'unknown'}\``, | |
| `- remaining violations: \`${remaining}\``, | |
| `- required issues evaluated: \`${report.summary?.requiredIssueCount ?? 0}\``, | |
| `- auto-assigned this run: \`${report.summary?.assignedDefaultMilestoneCount ?? 0}\``, | |
| `- failed assignments: \`${report.summary?.failedAssignmentsCount ?? 0}\``, | |
| `- default milestone created this run: \`${report.milestones?.createdDefaultMilestone ?? false}\`` | |
| ]; | |
| const errors = Array.isArray(report.execution?.errors) ? report.execution.errors : []; | |
| if (errors.length > 0) { | |
| lines.push(`- execution errors: \`${errors.length}\``); | |
| } | |
| if (process.env.GITHUB_STEP_SUMMARY) { | |
| fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${lines.join('\\n')}\\n`); | |
| } | |
| console.log(lines.join('\\n')); | |
| NODE | |
| - name: Upload milestone hygiene report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: issue-milestone-hygiene-report | |
| path: tests/results/_agent/issue/milestone-hygiene-report.json | |
| if-no-files-found: error |