Argus — Extensibility Request Triage (Scheduled) #41
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: Argus — Extensibility Request Triage (Scheduled) | |
| on: | |
| schedule: | |
| - cron: '0 1/6 * * *' # 4× daily: 01:00, 07:00, 13:00, 19:00 UTC | |
| workflow_dispatch: | |
| permissions: | |
| issues: write | |
| contents: read | |
| id-token: write | |
| # ── Job 1: discover eligible issues ───────────────────────────────────────── | |
| jobs: | |
| discover: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| issues: ${{ steps.find-issues.outputs.issues }} | |
| steps: | |
| - name: Find eligible issues | |
| id: find-issues | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| REPO="${{ github.repository }}" | |
| NOW=$(date -u '+%s') | |
| # ISO 8601 UTC strings — sort lexicographically == chronologically | |
| CUTOFF_5MIN=$(date -u -d "@$((NOW - 300))" '+%Y-%m-%dT%H:%M:%SZ') # now − 5 min | |
| CUTOFF_30D=$(date -u -d "@$((NOW - 2592000))" '+%Y-%m-%dT%H:%M:%SZ') # now − 30 days | |
| NEW_ISSUES=() | |
| UPDATED_ISSUES=() | |
| STALE_ISSUES=() | |
| # ── Helper: fetch all comments and extract last comment author/body ── | |
| fetch_comments() { | |
| local num=$1 | |
| ALL_COMMENTS=$(gh api "repos/$REPO/issues/$num/comments" \ | |
| --paginate --jq '.[]' 2>/dev/null || true) | |
| LAST_COMMENT=$(echo "$ALL_COMMENTS" | tail -1) | |
| LAST_COMMENT_AUTHOR=$(echo "$LAST_COMMENT" | jq -r '.user.login // ""' 2>/dev/null || true) | |
| LAST_COMMENT_BODY=$(echo "$LAST_COMMENT" | jq -r '.body // ""' 2>/dev/null || true) | |
| } | |
| # ── Helper: check if ANY comment starts with /not-accurate ───────── | |
| has_not_accurate() { | |
| echo "$ALL_COMMENTS" | jq -r '.body // ""' 2>/dev/null | grep -qiE '^\s*/not-accurate' | |
| } | |
| # ── Helper: check if author is a bot or AleksandricMarko ─────────── | |
| is_bot_or_marko() { | |
| local author=$1 | |
| [[ "$author" == *"[bot]"* || "$author" == "AleksandricMarko" ]] | |
| } | |
| while IFS= read -r ISSUE; do | |
| NUMBER=$( echo "$ISSUE" | jq -r '.number') | |
| TYPE=$( echo "$ISSUE" | jq -r '.type.name // ""') | |
| LABEL_COUNT=$(echo "$ISSUE" | jq '.labels | length') | |
| LABEL_NAME=$( echo "$ISSUE" | jq -r '.labels[0].name // ""') | |
| CREATED_AT=$( echo "$ISSUE" | jq -r '.created_at') | |
| UPDATED_AT=$( echo "$ISSUE" | jq -r '.updated_at') | |
| # ── Common gate: must be an open Task with 0 labels or only missing-info ── | |
| [ "$TYPE" != "Task" ] && continue | |
| if [ "$LABEL_COUNT" -gt 1 ]; then | |
| continue | |
| elif [ "$LABEL_COUNT" -eq 1 ] && [ "$LABEL_NAME" != "missing-info" ]; then | |
| continue | |
| fi | |
| # ── Fetch all comments (shared by all categories) ──────────────── | |
| fetch_comments "$NUMBER" | |
| # Skip if last comment contains /not-accurate | |
| if has_not_accurate; then | |
| continue | |
| fi | |
| if [ "$LABEL_COUNT" -eq 0 ]; then | |
| # ── NEW: no labels, created at least 5 min ago ───────────────── | |
| [[ "$CREATED_AT" < "$CUTOFF_5MIN" ]] || continue | |
| NEW_ISSUES+=("$NUMBER") | |
| elif [ "$LABEL_COUNT" -eq 1 ] && [ "$LABEL_NAME" = "missing-info" ]; then | |
| if [[ "$UPDATED_AT" < "$CUTOFF_30D" ]]; then | |
| # ── STALE: 30+ days without activity, last comment from bot or AleksandricMarko ── | |
| if is_bot_or_marko "$LAST_COMMENT_AUTHOR"; then | |
| STALE_ISSUES+=("$NUMBER") | |
| fi | |
| elif [[ "$UPDATED_AT" < "$CUTOFF_5MIN" ]]; then | |
| # ── UPDATED: has missing-info, updated ≥5 min ago, last comment NOT from bot/Marko ── | |
| if [[ -z "$LAST_COMMENT_AUTHOR" ]] || ! is_bot_or_marko "$LAST_COMMENT_AUTHOR"; then | |
| UPDATED_ISSUES+=("$NUMBER") | |
| fi | |
| fi | |
| fi | |
| done < <(gh api "repos/$REPO/issues" \ | |
| --paginate -X GET -f state=open -f per_page=100 --jq '.[]') | |
| # ── Log per-category results ─────────────────────────────────────── | |
| echo "::group::📋 New issues (${#NEW_ISSUES[@]})" | |
| [ "${#NEW_ISSUES[@]}" -gt 0 ] && printf ' #%s\n' "${NEW_ISSUES[@]}" || echo " (none)" | |
| echo "::endgroup::" | |
| echo "::group::🔄 Updated issues (${#UPDATED_ISSUES[@]})" | |
| [ "${#UPDATED_ISSUES[@]}" -gt 0 ] && printf ' #%s\n' "${UPDATED_ISSUES[@]}" || echo " (none)" | |
| echo "::endgroup::" | |
| echo "::group::⏳ Stale issues (${#STALE_ISSUES[@]})" | |
| [ "${#STALE_ISSUES[@]}" -gt 0 ] && printf ' #%s\n' "${STALE_ISSUES[@]}" || echo " (none)" | |
| echo "::endgroup::" | |
| # ── Combine all categories into a single output for the matrix ───── | |
| ALL=("${NEW_ISSUES[@]}" "${UPDATED_ISSUES[@]}" "${STALE_ISSUES[@]}") | |
| if [ "${#ALL[@]}" -eq 0 ]; then | |
| echo "No eligible issues found." | |
| echo "issues=[]" >> "$GITHUB_OUTPUT" | |
| else | |
| ISSUES_JSON=$(printf '%s\n' "${ALL[@]}" | jq -R 'tonumber' | jq -sc '.') | |
| echo "Found ${#ALL[@]} eligible issue(s): new=${#NEW_ISSUES[@]} updated=${#UPDATED_ISSUES[@]} stale=${#STALE_ISSUES[@]}" | |
| echo "issues=$ISSUES_JSON" >> "$GITHUB_OUTPUT" | |
| fi | |
| # ── Job 2: process each issue on its own runner ────────────────────────────── | |
| process: | |
| needs: discover | |
| if: needs.discover.outputs.issues != '' && needs.discover.outputs.issues != '[]' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| issue: ${{ fromJson(needs.discover.outputs.issues) }} | |
| fail-fast: false # one failing issue must not block the others | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Checkout BCAppsTriage source | |
| uses: actions/checkout@v5 | |
| with: | |
| repository: ${{ github.repository_owner }}/BCAppsTriage | |
| token: ${{ secrets.EXT_TRIAGE_SOURCE_TOKEN }} | |
| path: _triage-src | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: '22' | |
| - name: Install dependencies | |
| working-directory: _triage-src/internal/Argus_Triage_Extensibility_Requests | |
| run: npm ci | |
| - name: Install tsx globally | |
| run: npm install -g tsx | |
| - name: Make process-issue script executable | |
| run: chmod +x _triage-src/internal/Argus_Triage_Extensibility_Requests/scripts/process-issue.sh | |
| - name: Azure login (OIDC) | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| - name: Process issue #${{ matrix.issue }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| COPILOT_GITHUB_TOKEN: ${{ secrets.EXT_TRIAGE_COPILOT_TOKEN }} | |
| REPO_READ_TOKEN: ${{ secrets.EXT_TRIAGE_CODEBASE_TOKEN }} | |
| run: | | |
| bash _triage-src/internal/Argus_Triage_Extensibility_Requests/scripts/process-issue.sh \ | |
| "${{ matrix.issue }}" \ | |
| "${{ github.repository }}" \ | |
| "$(pwd)/_triage-src" | |
| - name: Upload result artifact | |
| if: always() | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: argus-result-${{ github.run_id }}-issue-${{ matrix.issue }} | |
| path: _triage-src/internal/Argus_Triage_Extensibility_Requests/data/argus_result.json | |
| retention-days: 30 | |
| if-no-files-found: ignore |