Skip to content

Argus — Extensibility Request Triage (Scheduled) #41

Argus — Extensibility Request Triage (Scheduled)

Argus — Extensibility Request Triage (Scheduled) #41

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