5555
5656 gh pr list --repo posit-dev/vip --state merged \
5757 --search "merged:$WEEK_START_TS..$WEEK_END_TS" \
58- --json number,title,author,labels,url \
58+ --json number,title,author,labels,url,additions,deletions,changedFiles \
5959 --limit 300 > all-prs.json
6060
6161 # Exclude fully-automated actors (dependabot version bumps, the
7474 echo "Found $PR_COUNT merged PRs for $WEEK_START..$WEEK_END."
7575 jq -r '.[] | "#\(.number) \(.title)"' weekly-prs.json || true
7676
77+ # Releases published in the same window, as an oldest→newest range
78+ # (e.g. "v0.52.4 to v0.54.6"). Drafts and prereleases are excluded.
79+ gh release list --repo posit-dev/vip \
80+ --json tagName,publishedAt,isDraft,isPrerelease --limit 100 > all-releases.json
81+ RELEASES=$(jq -r --arg start "$WEEK_START_TS" --arg end "$WEEK_END_TS" '
82+ [ .[]
83+ | select(.isDraft == false and .isPrerelease == false)
84+ | select(.publishedAt >= $start and .publishedAt <= $end) ]
85+ | sort_by(.publishedAt)
86+ | if length == 0 then ""
87+ elif length == 1 then .[0].tagName
88+ else "\(.[0].tagName) to \(.[-1].tagName)" end' all-releases.json)
89+ echo "releases=$RELEASES" >> "$GITHUB_OUTPUT"
90+ echo "Releases in window: ${RELEASES:-(none)}"
91+
7792 - name : Configure AWS credentials
7893 if : steps.gather.outputs.has_prs == 'true'
7994 uses : aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6
@@ -103,7 +118,7 @@ jobs:
103118 --model us.anthropic.claude-opus-4-8
104119 --fallback-model us.anthropic.claude-sonnet-4-6
105120 --allowedTools Read
106- --json-schema '{"type":"object","additionalProperties":false,"required":["highlights"],"properties":{"highlights":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["text","number"],"properties":{"text":{"type":"string"},"number":{"type":["integer","null"]}}}}}}'
121+ --json-schema '{"type":"object","additionalProperties":false,"required":["highlights"],"properties":{"highlights":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["text","number","category" ],"properties":{"text":{"type":"string"},"number":{"type":["integer","null"]},"category":{"type":"string","enum":["feature","fix "]}}}}}}'
107122
108123 - name : Build Slack payload
109124 if : steps.gather.outputs.has_prs == 'true'
@@ -113,6 +128,7 @@ jobs:
113128 WEEK_START : ${{ steps.gather.outputs.week_start }}
114129 WEEK_END : ${{ steps.gather.outputs.week_end }}
115130 PR_COUNT : ${{ steps.gather.outputs.pr_count }}
131+ RELEASES : ${{ steps.gather.outputs.releases }}
116132 run : |
117133 set -euo pipefail
118134 # Only claim has_highlights when a highlight with non-empty text
@@ -136,26 +152,36 @@ jobs:
136152 # Escape Slack mrkdwn control chars in Claude-derived text (& first) so a
137153 # crafted PR title echoed into a highlight cannot trigger <!channel>/<@user>
138154 # mentions or fake links; the trusted PR link is appended separately.
155+ # Carry each highlight's category so we can split it into two sections;
156+ # anything not tagged "feature" falls back to the fixes/chores group.
139157 LINKED=$(printf '%s' "$FILTERED" | jq -c --slurpfile prs weekly-prs.json '
140158 map(. as $h
141159 | ($h.text | gsub("&";"&") | gsub("<";"<") | gsub(">";">")) as $t
142160 | ([$prs[0][] | select(.number == $h.number) | .url] | first) as $u
143- | if $u == null then $t
144- else "\($t) (<\($u)|#\($h.number)>)"
145- end)')
146- HIGHLIGHTS=$(printf '%s' "$LINKED" | jq 'map("• " + .) | join("\n")')
161+ | { category: (if $h.category == "feature" then "feature" else "fix" end),
162+ line: (if $u == null then $t else "\($t) (<\($u)|PR #\($h.number)>)" end) })')
163+ # Two Slack sections: features/tests first, then fixes/chores.
164+ FEATURES=$(printf '%s' "$LINKED" | jq -r '[.[] | select(.category == "feature") | "• " + .line] | join("\n")')
165+ FIXES=$(printf '%s' "$LINKED" | jq -r '[.[] | select(.category == "fix") | "• " + .line] | join("\n")')
147166 jq -n \
148- --argjson highlights "$HIGHLIGHTS" \
149167 --arg week_start "$WEEK_START" \
150168 --arg week_end "$WEEK_END" \
151169 --arg pr_count "$PR_COUNT" \
170+ --arg releases "$RELEASES" \
171+ --arg features "$FEATURES" \
172+ --arg fixes "$FIXES" \
152173 '{
153174 text: "vip Weekly Summary",
154- blocks: [
175+ blocks: ( [
155176 {type: "header", text: {type: "plain_text", text: "🧪 vip Weekly Summary", emoji: true}},
156- {type: "context", elements: [{type: "mrkdwn", text: ("*" + $week_start + "* to *" + $week_end + "* | " + $pr_count + " merged PRs")}]},
157- {type: "section", text: {type: "mrkdwn", text: ("*Highlights*\n" + $highlights)}}
177+ {type: "context", elements: [{type: "mrkdwn", text: (
178+ "*\($week_start)* to *\($week_end)* | <https://github.com/posit-dev/vip/pulls?q=is%3Apr+is%3Aclosed|\($pr_count) merged PRs>"
179+ + (if $releases == "" then "" else " | <https://github.com/posit-dev/vip/releases|\($releases)>" end)
180+ )}]}
158181 ]
182+ + (if $features == "" then [] else [{type: "section", text: {type: "mrkdwn", text: ("*🚀 Features & tests*\n" + $features)}}] end)
183+ + (if $fixes == "" then [] else [{type: "section", text: {type: "mrkdwn", text: ("*🔧 Fixes & chores*\n" + $fixes)}}] end)
184+ )
159185 }' > /tmp/slack-payload.json
160186 echo "Slack payload (posted only on schedule/workflow_dispatch):"
161187 cat /tmp/slack-payload.json
0 commit comments