weekly-digest.yml

Monday Teams digest workflow. Lives in the redline-metrics repo.

What this is

Posts a Monday-morning stakeholder digest (open/stale PR counts, seed recall, dashboard link) to Teams as an Adaptive Card, via scripts/build-digest.mjs.

How to onboard it

Copy this file into the redline-metrics repository and set REDLINE_ORG_READ_TOKEN and TEAMS_WEBHOOK_URL there. The webhook must be a Power Automate "When a Teams webhook request is received" flow; the retired Office 365 connector shape no longer delivers.

  • Lives in: The redline-metrics repo — not this one.
  • Trigger: Every Monday at 07:00 UTC, plus workflow_dispatch.

How to use it

Nothing, normally — it runs itself every Monday. Run scripts/build-digest.mjs locally to preview digest.json before changing what the card reports.

What a run does, in order:

  • Counts open and stale (>7 days untouched) org PRs via the GitHub search API.
  • Builds the Adaptive Card with scripts/build-digest.mjs --out digest.json.
  • POSTs it to TEAMS_WEBHOOK_URL — a Power Automate "When a Teams webhook request is received" flow; the old Office 365 connector shape no longer delivers. Fails the step outright if the webhook secret isn't set.

Works, but only where installed: needs REDLINE_ORG_READ_TOKEN and TEAMS_WEBHOOK_URL. Delivers an empty-looking digest on a repo with no collected telemetry yet.

Expected output

An Adaptive Card posted to Teams each Monday: open and stale PR counts, seed recall, and a dashboard link. With no collected telemetry yet the card still posts and reads empty. A missing webhook secret fails the step outright rather than posting nothing quietly.

How to edit it

  1. Edit the YAML in workflows/ or .github/workflows/workflows/ holds files destined for other repositories; .github/workflows/ is this repository's own CI. The two are not interchangeable — check where this one lives before editing.
  2. actionlintCI lints .github/workflows/*.yml, workflows/*.yml and templates/redline.yml together. workflows/ is pointed at explicitly because actionlint's own discovery would skip it.
  3. node scripts/check-pins.mjsIf you add a third-party action, pin it to a 40-character commit SHA with a trailing # vX.Y.Z comment. First-party actions/* are referenced by tag. The pin checker re-resolves the SHA against the tag the comment claims.
  4. node scripts/validate.mjsAsserts the workflow files the bundle depends on still exist, and that the gate's job ids still match the required check name derived from them.

The full file

workflows/weekly-digest.yml · 79 lines · 2.5 KB
# Lives in the redline-metrics repo. Monday-morning stakeholder digest.
#
# TEAMS_WEBHOOK_URL must point at a Power Automate "When a Teams webhook request is
# received" flow. The old Office 365 connector webhooks (the `{"text": "..."}` shape)
# were retired by Microsoft and silently stop delivering.
name: Redline Weekly Digest

on:
  schedule:
    - cron: '0 7 * * 1'
  workflow_dispatch:

permissions:
  contents: read

jobs:
  digest:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Count open and stale PRs
        id: prs
        env:
          GH_TOKEN: ${{ secrets.REDLINE_ORG_READ_TOKEN }}
          ORG: ${{ github.repository_owner }}
        run: |
          set -euo pipefail
          cutoff=$(date -u -d '7 days ago' +%Y-%m-%d)

          # gh search caps at 1000 results; --json + jq length would silently truncate.
          # total_count from the REST search endpoint is exact.
          count() {
            gh api -X GET search/issues -f q="$1" -f per_page=1 --jq '.total_count'
          }
          open_prs=$(count "org:$ORG is:pr is:open draft:false")
          stale_prs=$(count "org:$ORG is:pr is:open draft:false updated:<$cutoff")

          echo "open=$open_prs"  >> "$GITHUB_OUTPUT"
          echo "stale=$stale_prs" >> "$GITHUB_OUTPUT"

      - name: Build Adaptive Card
        run: |
          npx --yes "redlinegate@0.0.3" metrics digest \
            --org "${{ github.repository_owner }}" \
            --days 7 \
            --open-prs "${{ steps.prs.outputs.open }}" \
            --stale-prs "${{ steps.prs.outputs.stale }}" \
            --seed-scores data/seed-scores.jsonl \
            --dashboard-url "${{ vars.REDLINE_DASHBOARD_URL }}" \
            --out digest.json

      - name: Post to Teams
        env:
          WEBHOOK: ${{ secrets.TEAMS_WEBHOOK_URL }}
        run: |
          set -euo pipefail
          if [[ -z "${WEBHOOK:-}" ]]; then
            echo "::error::TEAMS_WEBHOOK_URL is not set — digest built but not delivered."
            exit 1
          fi
          curl -sS --fail-with-body -X POST \
            -H 'Content-Type: application/json' \
            --data @digest.json "$WEBHOOK"

      - name: Attach digest to the run
        if: always()
        run: |
          {
            echo '### Digest payload'
            echo '```json'
            cat digest.json 2>/dev/null || echo '{}'
            echo '```'
          } >> "$GITHUB_STEP_SUMMARY"