From b7c766df5c55eb008d24349e516832c3cf09c432 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 14:15:14 +1100 Subject: [PATCH 1/8] feat: add comment summary template for activity report highlights - New --summary-template flag and summary-template action input - Collects merged PR titles, URLs, authors, and labels during processing - Generates comment-template.md with PRs grouped by repository - Includes placeholder sections for human-written thematic summaries - Labels displayed in brackets to aid categorization - New comment-template action output for downstream workflow steps - Works with both org repos and external repositories - Added docs/comment-summary-template.md with usage guide --- .gitignore | 2 + CHANGELOG.md | 10 ++ README.md | 14 +++ action.yml | 15 ++- docs/README.md | 7 ++ docs/comment-summary-template.md | 109 ++++++++++++++++++++ generate-report.sh | 166 ++++++++++++++++++++++++++++++- 7 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 docs/comment-summary-template.md diff --git a/.gitignore b/.gitignore index abca477..f47cddd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Generated reports from local testing report.md weekly-report.md +comment-template.md *.md.bak # macOS @@ -15,3 +16,4 @@ weekly-report.md # Test outputs test-output/ +.tmp/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d26650..f254161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **Comment summary template**: Auto-generate an editable highlights template from merged PR data + - New `--summary-template` CLI flag and `summary-template` action input + - New `--summary-template-file=FILE` CLI flag and `summary-template-file` action input + - Collects merged PR titles, URLs, authors, and labels during report generation + - Groups PRs by repository with placeholder sections for human curation + - New `comment-template` action output for downstream workflow steps + - Labels from PRs are included to aid thematic categorization + - See [Comment Summary Template docs](docs/comment-summary-template.md) for details + ## [2.2.0] - 2025-10-23 ### Added diff --git a/README.md b/README.md index e7d6fdb..033bb5d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ A powerful GitHub Action that generates comprehensive activity reports across Gi - **📊 Comprehensive Analytics**: Issues, PRs, commits, and activity summaries - **🔗 Clickable Metrics**: Numbers > 0 link directly to GitHub search results for quick navigation +- **📝 Comment Summary Template**: Auto-generates an editable highlights template with merged PR details and labels - **🌐 External Repository Tracking**: Monitor repositories outside your primary organization - **⚡ Smart Activity Detection**: Captures ALL repository activity (commits, issues, PRs, updates) with intelligent filtering - **🔄 Complete Coverage**: Handles organizations with hundreds of repositories via pagination @@ -86,6 +87,12 @@ export GITHUB_TOKEN=ghp_xxxxx # Track external repositories (from other organizations) ./generate-report.sh --token=ghp_xxxxx --track-external-repos=executablebooks/sphinx-proof,executablebooks/sphinx-exercise +# Generate report with comment summary template +./generate-report.sh --token=ghp_xxxxx --summary-template + +# Custom summary template output file +./generate-report.sh --token=ghp_xxxxx --summary-template --summary-template-file=highlights.md + # View the generated report cat report.md ``` @@ -105,6 +112,8 @@ cat report.md - `--exclude=REPOS` - Comma-separated list of repos or regex patterns to exclude (e.g., `repo1,lecture-.*\.notebooks`) - `--track-external-repos=LIST` - Comma-separated list of external repos to track (format: `org/repo`, e.g., `executablebooks/sphinx-proof,executablebooks/sphinx-exercise`) - `--delay=SECONDS` - Delay between API calls (default: 0) +- `--summary-template` - Generate a comment summary template with merged PR details +- `--summary-template-file=FILE` - Output filename for summary template (default: `comment-template.md`) The report is saved to `report.md` (or your specified output file) in the current directory. @@ -118,6 +127,8 @@ The report is saved to `report.md` (or your specified output file) in the curren | `exclude-repos` | Comma-separated list of repository names or regex patterns to exclude (e.g., `repo1,lecture-.*\.notebooks`) | No | `''` | | `track-external-repos` | Comma-separated list of external repositories to track (format: `org/repo`, e.g., `executablebooks/sphinx-proof,executablebooks/sphinx-exercise`) | No | `''` | | `api-delay` | Delay in seconds between API calls to avoid rate limits (0 = no delay) | No | `0` | +| `summary-template` | Generate a comment summary template with merged PR details (`true`/`false`) | No | `false` | +| `summary-template-file` | Output filename for the comment summary template | No | `comment-template.md` | ## Outputs @@ -125,6 +136,7 @@ The report is saved to `report.md` (or your specified output file) in the curren |--------|-------------| | `report-content` | The full generated report content | | `report-summary` | A brief summary of the report metrics | +| `comment-template` | The generated comment summary template content (when `summary-template` is enabled) | ## How It Works @@ -178,6 +190,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} organization: 'QuantEcon' track-external-repos: 'executablebooks/sphinx-proof,executablebooks/sphinx-exercise' + summary-template: 'true' # Generate highlights template # Step 2: Create issue with report (separate action - optional) - name: Create issue from report @@ -207,6 +220,7 @@ For large organizations, use the `api-delay` parameter to add delays between req ## Documentation - **[Testing Guide](docs/testing.md)** - How to test and validate the action +- **[Comment Summary Template](docs/comment-summary-template.md)** - Auto-generated highlights from merged PRs - **[Technical Details](docs/improvements.md)** - Implementation details - **[Validation Examples](docs/validation.md)** - Real-world validation - **[Release Notes](docs/releases/)** - Version-specific changes diff --git a/action.yml b/action.yml index 7ff09d9..56d49b9 100644 --- a/action.yml +++ b/action.yml @@ -26,6 +26,14 @@ inputs: description: 'Delay in seconds between API calls to avoid rate limits (0 = no delay)' required: false default: '0' + summary-template: + description: 'Generate a comment summary template with merged PR details (true/false)' + required: false + default: 'false' + summary-template-file: + description: 'Output filename for the comment summary template' + required: false + default: 'comment-template.md' outputs: report-content: @@ -34,6 +42,9 @@ outputs: report-summary: description: 'A brief summary of the report metrics' value: ${{ steps.generate.outputs.report-summary }} + comment-template: + description: 'The generated comment summary template content (when summary-template is enabled)' + value: ${{ steps.generate.outputs.comment-template }} runs: using: 'composite' @@ -48,4 +59,6 @@ runs: INPUT_OUTPUT_FORMAT: ${{ inputs.output-format }} INPUT_EXCLUDE_REPOS: ${{ inputs.exclude-repos }} INPUT_TRACK_EXTERNAL_REPOS: ${{ inputs.track-external-repos }} - INPUT_API_DELAY: ${{ inputs.api-delay }} \ No newline at end of file + INPUT_API_DELAY: ${{ inputs.api-delay }} + INPUT_SUMMARY_TEMPLATE: ${{ inputs.summary-template }} + INPUT_SUMMARY_TEMPLATE_FILE: ${{ inputs.summary-template-file }} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 8abf4af..22eb5e2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,6 +4,7 @@ - [Testing Guide](testing.md) - How to test and validate the action - [Hyperlink Feature](hyperlinks.md) - Interactive metrics with clickable links +- [Comment Summary Template](comment-summary-template.md) - Auto-generated highlights from merged PRs - [Technical Details](improvements.md) - Implementation and architecture - [Validation](validation.md) - Real-world examples and verification - [Release Notes](releases/) - Version history and changes @@ -24,6 +25,12 @@ - URL formats and destinations - Usage examples +**[Comment Summary Template](comment-summary-template.md)** +- Auto-generate highlights from merged PR data +- Label-aware categorization +- Editable template workflow +- Action outputs for downstream steps + ### Technical Documentation **[Implementation Details](improvements.md)** diff --git a/docs/comment-summary-template.md b/docs/comment-summary-template.md new file mode 100644 index 0000000..0ad7c69 --- /dev/null +++ b/docs/comment-summary-template.md @@ -0,0 +1,109 @@ +# Comment Summary Template + +The comment summary template feature auto-generates an editable markdown template containing all merged PR details from the report period. This helps write the "highlights" comment that accompanies each activity report. + +## Background + +Activity reports generated by this action contain a metrics table (issues, PRs, commits). The accompanying comment — written by a maintainer — provides high-level context about what was accomplished. This feature bridges the gap by collecting the raw PR data needed to write that comment. + +See [QuantEcon/meta#280](https://github.com/QuantEcon/meta/issues/280) for an example of a report with a hand-written highlights comment. + +## Usage + +### CLI + +```bash +# Generate report with comment summary template +./generate-report.sh --token=ghp_xxxxx --summary-template + +# Custom output filename +./generate-report.sh --token=ghp_xxxxx --summary-template --summary-template-file=highlights.md +``` + +### GitHub Action + +```yaml +- name: Generate activity report + uses: QuantEcon/action-weekly-report@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + summary-template: 'true' + summary-template-file: 'comment-template.md' +``` + +## Output Format + +The template is written to `comment-template.md` (default) and contains: + +1. **Placeholder summary** — Editable topic bullets for high-level themes +2. **Merged PRs by repository** — Every merged PR with title, URL, labels, and author +3. **Contributor acknowledgement** — Template for thanking contributors + +### Example Output + +```markdown +**High Level Summary:** + +1. **[Topic 1]** _Add a high-level summary of work in this area._ +2. **[Topic 2]** _Add another thematic summary._ + +--- + +**lecture-python.myst:** + - Update consumer problem lecture https://github.com/QuantEcon/lecture-python.myst/pull/757 [maintenance] (@HumphreyYang) + - Fix typo in GDP lecture https://github.com/QuantEcon/lecture-python.myst/pull/756 (@contributor) + +**quantecon-book-theme:** + - Major refactor for responsiveness https://github.com/QuantEcon/quantecon-book-theme/pull/335 [infrastructure, priority: high] (@mmcky) + +--- + +thanks @contributor1 and @contributor2 for all your contributions. +``` + +## How Labels Help + +When PRs have labels (e.g., `maintenance`, `infrastructure`, `bug`, `priority: high`), they appear in brackets next to each PR. This makes it easier to: + +- **Group by theme** — Quickly identify which PRs relate to maintenance vs. new features +- **Prioritize highlights** — Spot high-priority work at a glance +- **Categorize** — Reorganize the flat per-repo list into thematic sections + +For maximum benefit, use standardized labels across repositories. See [QuantEcon/meta#178](https://github.com/QuantEcon/meta/issues/178) for the labels standardization initiative. + +## Workflow + +1. Run the action with `summary-template: 'true'` +2. Open `comment-template.md` — all merged PRs are listed by repo +3. Reorganize into thematic sections (Lectures, Infrastructure, Libraries, etc.) +4. Add high-level context for each section +5. Remove PRs that don't need highlighting (e.g., automated dependency bumps) +6. Post as a comment on the activity report issue + +## Action Outputs + +When `summary-template` is enabled, the template content is also available as the `comment-template` output for use in downstream workflow steps: + +```yaml +- name: Generate report + id: report + uses: QuantEcon/action-weekly-report@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + summary-template: 'true' + +- name: Use template content + run: echo "${{ steps.report.outputs.comment-template }}" +``` + +## Inputs + +| Input | Description | Default | +|-------|-------------|---------| +| `summary-template` | Enable template generation (`true`/`false`) | `false` | +| `summary-template-file` | Output filename | `comment-template.md` | + +| CLI Flag | Description | +|----------|-------------| +| `--summary-template` | Enable template generation | +| `--summary-template-file=FILE` | Output filename (default: `comment-template.md`) | diff --git a/generate-report.sh b/generate-report.sh index 7adbece..b9fc343 100755 --- a/generate-report.sh +++ b/generate-report.sh @@ -7,6 +7,8 @@ END_DATE="" CLI_TOKEN="" CLI_ORG="" CLI_OUTPUT="" +CLI_SUMMARY_TEMPLATE="" +CLI_SUMMARY_TEMPLATE_FILE="" show_usage() { cat << EOF @@ -23,6 +25,8 @@ OPTIONS: --exclude=REPOS Comma-separated list of repos to exclude (supports regex patterns) --track-external-repos=LIST Comma-separated list of external repos (format: org/repo,org/repo) --delay=SECONDS Delay between API calls (default: 0) + --summary-template Generate a comment summary template with merged PR details + --summary-template-file=FILE Output filename for summary template (default: comment-template.md) --help Show this help message EXAMPLES: @@ -50,6 +54,12 @@ EXAMPLES: # Track external repositories $0 --token=ghp_xxxxx --track-external-repos=executablebooks/sphinx-proof,executablebooks/sphinx-exercise + # Generate report with comment summary template + $0 --token=ghp_xxxxx --summary-template + + # Custom summary template filename + $0 --token=ghp_xxxxx --summary-template --summary-template-file=highlights.md + ENVIRONMENT VARIABLES: GITHUB_TOKEN or INPUT_GITHUB_TOKEN GitHub token INPUT_ORGANIZATION Organization name @@ -57,6 +67,8 @@ ENVIRONMENT VARIABLES: INPUT_EXCLUDE_REPOS Repositories to exclude INPUT_TRACK_EXTERNAL_REPOS External repositories to track INPUT_API_DELAY API delay in seconds + INPUT_SUMMARY_TEMPLATE Enable summary template generation (true/false) + INPUT_SUMMARY_TEMPLATE_FILE Summary template output filename OUTPUT: Report is saved to: report.md (or specified --output file) @@ -99,6 +111,14 @@ for arg in "$@"; do CLI_OUTPUT="${arg#*=}" shift ;; + --summary-template) + CLI_SUMMARY_TEMPLATE="true" + shift + ;; + --summary-template-file=*) + CLI_SUMMARY_TEMPLATE_FILE="${arg#*=}" + shift + ;; --help) show_usage exit 0 @@ -121,6 +141,8 @@ OUTPUT_FORMAT="${INPUT_OUTPUT_FORMAT:-markdown}" EXCLUDE_REPOS="${CLI_EXCLUDE:-${INPUT_EXCLUDE_REPOS:-}}" TRACK_EXTERNAL_REPOS="${CLI_EXTERNAL_REPOS:-${INPUT_TRACK_EXTERNAL_REPOS:-}}" API_DELAY="${CLI_DELAY:-${INPUT_API_DELAY:-0}}" +SUMMARY_TEMPLATE="${CLI_SUMMARY_TEMPLATE:-${INPUT_SUMMARY_TEMPLATE:-false}}" +SUMMARY_TEMPLATE_FILE="${CLI_SUMMARY_TEMPLATE_FILE:-${INPUT_SUMMARY_TEMPLATE_FILE:-comment-template.md}}" # Set output file based on context # - GitHub Actions: Use weekly-report.md for backward compatibility @@ -149,6 +171,7 @@ fi echo "DEBUG: Inputs - ORG: $ORGANIZATION, FORMAT: $OUTPUT_FORMAT, EXCLUDE: $EXCLUDE_REPOS" echo "DEBUG: External repos: $TRACK_EXTERNAL_REPOS" echo "DEBUG: Output file: $OUTPUT_FILE" +echo "DEBUG: Summary template: $SUMMARY_TEMPLATE (file: $SUMMARY_TEMPLATE_FILE)" # Validate external repos format (must be org/repo) if [ -n "$TRACK_EXTERNAL_REPOS" ]; then @@ -311,6 +334,26 @@ process_external_repo() { local merged_prs=0 merged_prs=$(echo "$prs_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.merged_at != null and .merged_at >= $since and .merged_at <= $until)] | length else 0 end' 2>/dev/null || echo 0) + # Collect merged PR details for summary template + local local_ext_pr_details="" + if [ "$SUMMARY_TEMPLATE" = "true" ] && [ "$merged_prs" -gt 0 ]; then + local_ext_pr_details=$(echo "$prs_response" | jq -c --arg since "$WEEK_AGO" --arg until "$NOW" --arg repo "$repo_name" --arg org "$repo_org" ' + if type == "array" then + [.[] | select(.merged_at != null and .merged_at >= $since and .merged_at <= $until) | + { + repo: ($org + "/" + $repo), + org: $org, + number: .number, + title: .title, + url: .html_url, + author: .user.login, + labels: [.labels[]?.name], + merged_at: .merged_at + }] | .[] + else empty end + ' 2>/dev/null) + fi + # Count opened PRs in the date range local all_prs_response all_prs_response=$(api_call "/repos/${full_repo}/pulls?state=all") @@ -332,7 +375,12 @@ process_external_repo() { commits=${commits:-0} # Return values as JSON for easy parsing - echo "{\"repo\":\"$full_repo\",\"current_issues\":$current_issues,\"opened_issues\":$opened_issues,\"closed_issues\":$closed_issues,\"opened_prs\":$opened_prs,\"merged_prs\":$merged_prs,\"commits\":$commits}" + # Include PR details as a nested array for summary template + local pr_details_json="[]" + if [ -n "$local_ext_pr_details" ]; then + pr_details_json=$(echo "$local_ext_pr_details" | jq -s '.') + fi + echo "{\"repo\":\"$full_repo\",\"current_issues\":$current_issues,\"opened_issues\":$opened_issues,\"closed_issues\":$closed_issues,\"opened_prs\":$opened_prs,\"merged_prs\":$merged_prs,\"commits\":$commits,\"pr_details\":$pr_details_json}" } # Get all repositories and filter by activity @@ -527,6 +575,10 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then echo "DEBUG: Initial report content set, length: ${#report_content}" fi +# Initialize summary template data collection +# We collect merged PR details as newline-delimited JSON records +summary_template_data="" + # Process each repository repo_count=0 repos_with_activity=0 @@ -579,6 +631,33 @@ while IFS= read -r repo; do prs_response=$(api_call "/repos/${ORGANIZATION}/${repo}/pulls?state=closed") if [ $? -eq 0 ]; then merged_prs=$(echo "$prs_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.merged_at != null and .merged_at >= $since and .merged_at <= $until)] | length else 0 end') + + # Collect merged PR details for summary template + if [ "$SUMMARY_TEMPLATE" = "true" ] && [ "$merged_prs" -gt 0 ]; then + local_pr_details=$(echo "$prs_response" | jq -c --arg since "$WEEK_AGO" --arg until "$NOW" --arg repo "$repo" --arg org "$ORGANIZATION" ' + if type == "array" then + [.[] | select(.merged_at != null and .merged_at >= $since and .merged_at <= $until) | + { + repo: $repo, + org: $org, + number: .number, + title: .title, + url: .html_url, + author: .user.login, + labels: [.labels[]?.name], + merged_at: .merged_at + }] | .[] + else empty end + ' 2>/dev/null) + if [ -n "$local_pr_details" ]; then + if [ -n "$summary_template_data" ]; then + summary_template_data="${summary_template_data} +${local_pr_details}" + else + summary_template_data="$local_pr_details" + fi + fi + fi else merged_prs=0 if echo "$prs_response" | jq -e '.error' 2>/dev/null | grep -q "rate_limit"; then @@ -732,6 +811,19 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then merged_prs=$(echo "$repo_result" | jq -r '.merged_prs') commits=$(echo "$repo_result" | jq -r '.commits') + # Extract PR details for summary template + if [ "$SUMMARY_TEMPLATE" = "true" ]; then + ext_pr_details=$(echo "$repo_result" | jq -c '.pr_details[]?' 2>/dev/null) + if [ -n "$ext_pr_details" ]; then + if [ -n "$summary_template_data" ]; then + summary_template_data="${summary_template_data} +${ext_pr_details}" + else + summary_template_data="$ext_pr_details" + fi + fi + fi + # Add to totals ext_total_current_issues=$((ext_total_current_issues + current_issues)) ext_total_opened_issues=$((ext_total_opened_issues + opened_issues)) @@ -792,6 +884,70 @@ fi # Create summary summary="Summary: $total_current_issues current open issues, $total_opened_issues issues opened, $total_closed_issues issues closed, $total_opened_prs PRs opened, $total_merged_prs PRs merged, $total_commits commits" +# Generate comment summary template if enabled +if [ "$SUMMARY_TEMPLATE" = "true" ]; then + echo "Generating comment summary template..." + + template_content="**High Level Summary:** + + + +1. **[Topic 1]** _Add a high-level summary of work in this area._ +2. **[Topic 2]** _Add another thematic summary._ + +---" + + # Group PRs by repository and generate sections + if [ -n "$summary_template_data" ]; then + # Get unique repos from the collected data + repo_list=$(echo "$summary_template_data" | jq -r '.repo' | sort -u) + + while IFS= read -r template_repo; do + [ -z "$template_repo" ] && continue + + template_content="${template_content} + +**${template_repo}:**" + + # Get PRs for this repo, sorted by merged_at + repo_prs=$(echo "$summary_template_data" | jq -c --arg repo "$template_repo" 'select(.repo == $repo)' | jq -s 'sort_by(.merged_at) | reverse | .[]' -c) + + while IFS= read -r pr_json; do + [ -z "$pr_json" ] && continue + pr_title=$(echo "$pr_json" | jq -r '.title') + pr_url=$(echo "$pr_json" | jq -r '.url') + pr_author=$(echo "$pr_json" | jq -r '.author') + pr_labels=$(echo "$pr_json" | jq -r '.labels | if length > 0 then " [" + join(", ") + "]" else "" end') + + template_content="${template_content} + - ${pr_title} ${pr_url}${pr_labels} (@${pr_author})" + done <<< "$repo_prs" + + done <<< "$repo_list" + else + template_content="${template_content} + +_No merged PRs found in the report period._" + fi + + template_content="${template_content} + +--- + + +thanks @contributor1 and @contributor2 for all your contributions." + + # Save template to file + echo "$template_content" > "$SUMMARY_TEMPLATE_FILE" + echo "Comment summary template generated: $SUMMARY_TEMPLATE_FILE" + echo "DEBUG: Summary template length: ${#template_content}" +fi + # Save report to file echo "$report_content" > "$OUTPUT_FILE" @@ -816,6 +972,14 @@ if [ -n "$GITHUB_OUTPUT" ]; then echo "report-summary=$summary" >> "$GITHUB_OUTPUT" + # Add summary template output if enabled + if [ "$SUMMARY_TEMPLATE" = "true" ] && [ -n "$template_content" ]; then + template_delimiter="QUANTECON_TEMPLATE_END_$(date +%s)" + echo "comment-template<<${template_delimiter}" >> "$GITHUB_OUTPUT" + echo "$template_content" >> "$GITHUB_OUTPUT" + echo "${template_delimiter}" >> "$GITHUB_OUTPUT" + fi + echo "DEBUG: Outputs written to GITHUB_OUTPUT" echo "DEBUG: GITHUB_OUTPUT file size: $(wc -c < "$GITHUB_OUTPUT")" else From bfcaef139dc50af1e8a09072ab254df73804dfc8 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 14:43:59 +1100 Subject: [PATCH 2/8] docs: add auto-post workflow for comment summary template Update example workflow in README and feature docs to show the full 3-step flow: generate report + template, create issue, then post the template as the first comment using peter-evans/create-or-update-comment. --- README.md | 21 +++++++++++++----- docs/comment-summary-template.md | 38 ++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 033bb5d..515a101 100644 --- a/README.md +++ b/README.md @@ -183,24 +183,35 @@ jobs: weekly-report: runs-on: ubuntu-latest steps: - # Step 1: Generate the report (our action) + # Step 1: Generate the report and comment summary template - name: Generate weekly report - uses: QuantEcon/action-weekly-report@v2 # Always uses latest v2.x.x release + uses: QuantEcon/action-weekly-report@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} organization: 'QuantEcon' track-external-repos: 'executablebooks/sphinx-proof,executablebooks/sphinx-exercise' - summary-template: 'true' # Generate highlights template + summary-template: 'true' - # Step 2: Create issue with report (separate action - optional) + # Step 2: Create issue from report - name: Create issue from report + id: create-issue uses: peter-evans/create-issue-from-file@v4 with: title: Weekly Activity Report - content-filepath: weekly-report.md # Action outputs to weekly-report.md + content-filepath: weekly-report.md labels: report, automated + + # Step 3: Post the comment summary template on the new issue + - name: Post comment summary template + if: hashFiles('comment-template.md') != '' + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ steps.create-issue.outputs.issue-number }} + body-path: comment-template.md ``` +**How it works:** Step 1 generates both `weekly-report.md` and `comment-template.md`. Step 2 creates the issue. Step 3 posts the template as the first comment on that issue — ready for a maintainer to edit into a curated highlights summary. + **Note:** The action outputs to `weekly-report.md` by default. In CLI mode, it uses `report.md`. ## Report Format diff --git a/docs/comment-summary-template.md b/docs/comment-summary-template.md index 0ad7c69..cc97f77 100644 --- a/docs/comment-summary-template.md +++ b/docs/comment-summary-template.md @@ -73,7 +73,41 @@ For maximum benefit, use standardized labels across repositories. See [QuantEcon ## Workflow -1. Run the action with `summary-template: 'true'` +### Automated (GitHub Actions) + +The template can be auto-posted as a comment on the activity report issue: + +```yaml +# Step 1: Generate report + template +- name: Generate weekly report + uses: QuantEcon/action-weekly-report@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + summary-template: 'true' + +# Step 2: Create issue from report +- name: Create issue from report + id: create-issue + uses: peter-evans/create-issue-from-file@v4 + with: + title: Weekly Activity Report + content-filepath: weekly-report.md + labels: report, automated + +# Step 3: Post template as first comment +- name: Post comment summary template + if: hashFiles('comment-template.md') != '' + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ steps.create-issue.outputs.issue-number }} + body-path: comment-template.md +``` + +The template lands as the first comment on the new issue. A maintainer then edits that comment to curate the highlights. + +### Manual + +1. Run the action or CLI with `summary-template` enabled 2. Open `comment-template.md` — all merged PRs are listed by repo 3. Reorganize into thematic sections (Lectures, Infrastructure, Libraries, etc.) 4. Add high-level context for each section @@ -82,7 +116,7 @@ For maximum benefit, use standardized labels across repositories. See [QuantEcon ## Action Outputs -When `summary-template` is enabled, the template content is also available as the `comment-template` output for use in downstream workflow steps: +The template content is also available as the `comment-template` output for use in downstream workflow steps: ```yaml - name: Generate report From e6d43074017784849daf158e44fe4da4ec79a289 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 15:43:24 +1100 Subject: [PATCH 3/8] feat: auto-detect contributors from PRs and use markdown links - Extract unique non-bot authors from merged PR data - Format as 'thanks @user1, @user2, and @user3...' acknowledgement - Filter out [bot] accounts and Copilot from contributor list - Use markdown link format [PR Title](url) for clean GitHub rendering --- generate-report.sh | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/generate-report.sh b/generate-report.sh index b9fc343..8fe27d2 100755 --- a/generate-report.sh +++ b/generate-report.sh @@ -925,7 +925,7 @@ if [ "$SUMMARY_TEMPLATE" = "true" ]; then pr_labels=$(echo "$pr_json" | jq -r '.labels | if length > 0 then " [" + join(", ") + "]" else "" end') template_content="${template_content} - - ${pr_title} ${pr_url}${pr_labels} (@${pr_author})" + - [${pr_title}](${pr_url})${pr_labels} (@${pr_author})" done <<< "$repo_prs" done <<< "$repo_list" @@ -935,12 +935,41 @@ if [ "$SUMMARY_TEMPLATE" = "true" ]; then _No merged PRs found in the report period._" fi + # Build contributors line from actual PR authors + contributors_line="" + if [ -n "$summary_template_data" ]; then + # Get unique non-bot authors, sorted alphabetically + contributors=$(echo "$summary_template_data" | jq -r '.author' | grep -v '\[bot\]$' | grep -vi '^copilot$' | sort -uf) + contributor_count=$(echo "$contributors" | grep -c . || true) + + if [ "$contributor_count" -gt 0 ]; then + # Format as @user1, @user2, and @user3 + formatted="" + i=0 + while IFS= read -r author; do + [ -z "$author" ] && continue + i=$((i + 1)) + if [ $i -eq 1 ]; then + formatted="@${author}" + elif [ $i -eq "$contributor_count" ]; then + formatted="${formatted}, and @${author}" + else + formatted="${formatted}, @${author}" + fi + done <<< "$contributors" + contributors_line="thanks ${formatted} for all your contributions." + fi + fi + + if [ -z "$contributors_line" ]; then + contributors_line="" + fi + template_content="${template_content} --- - -thanks @contributor1 and @contributor2 for all your contributions." +${contributors_line}" # Save template to file echo "$template_content" > "$SUMMARY_TEMPLATE_FILE" From e94b6e9cc29285caa53da0b0446765072d6c5b0e Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 16:50:23 +1100 Subject: [PATCH 4/8] feat: add release tracking to comment summary template (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add release tracking to comment summary template - Fetch releases published in the report date range via GitHub API - Display releases with 📦 emoji at top of each repo section, before PRs - Include release name, tag, and link to GitHub release page - Works for both organization repos and external tracked repos - Update docs and CHANGELOG with release tracking details - Auto-detect contributors and use markdown links (carried from template branch) * refactor: use single releases table at top of template (Option A) - Replace per-repo inline release bullets with a single consolidated table - Table columns: Repository, Release (name/tag with link), Date - Sorted by publication date (newest first) - Releases section omitted entirely when no releases in the period - PR listings per repo remain unchanged below the table - Update docs example and CHANGELOG * feat: add Releases count column to report table - Fetch releases unconditionally (not just when summary-template enabled) - Add Releases column between Merged PRs and Commits in report table - Release count links to repo's releases page when > 0 - Include in totals row and Details section - Add to external repos table and totals - Include in summary string output * fix: address Copilot review feedback - Add rate_limited_repos/failed_repos error handling for releases API call - Use cross-platform date formatting (GNU first, BSD fallback) - Remove unused all_template_repos dead code block - Add CHANGELOG entry for Releases column in main report table --- CHANGELOG.md | 14 +++ docs/comment-summary-template.md | 21 ++-- generate-report.sh | 163 +++++++++++++++++++++++++++---- 3 files changed, 173 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f254161..57368a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Groups PRs by repository with placeholder sections for human curation - New `comment-template` action output for downstream workflow steps - Labels from PRs are included to aid thematic categorization + - Auto-detects contributors from merged PRs (filters bots and Copilot) + - Uses markdown link format for clean GitHub rendering - See [Comment Summary Template docs](docs/comment-summary-template.md) for details +- **Release tracking in summary template**: Repos that publish releases now show them in the template + - Fetches releases published in the report date range via GitHub API + - Displays a single releases table at the top of the template (omitted if no releases) + - Table columns: Repository, Release (name/tag with link), Date + - Sorted by publication date (newest first) + - Works for both organization repos and external tracked repos + - Helps summarize important milestones alongside PR activity +- **Releases column in main report**: Include release activity directly in the primary report table + - Adds a **Releases** column to the main activity table for each repository + - Counts non-draft releases published in the report date range and includes them in the totals row + - Clickable link to repo releases page when count > 0 + - Keeps release metrics alongside issues, PRs, and commits for a unified overview ## [2.2.0] - 2025-10-23 diff --git a/docs/comment-summary-template.md b/docs/comment-summary-template.md index cc97f77..1bfe0e8 100644 --- a/docs/comment-summary-template.md +++ b/docs/comment-summary-template.md @@ -36,8 +36,9 @@ See [QuantEcon/meta#280](https://github.com/QuantEcon/meta/issues/280) for an ex The template is written to `comment-template.md` (default) and contains: 1. **Placeholder summary** — Editable topic bullets for high-level themes -2. **Merged PRs by repository** — Every merged PR with title, URL, labels, and author -3. **Contributor acknowledgement** — Template for thanking contributors +2. **Releases table** — All releases published in the period in a single table (omitted if none) +3. **Merged PRs by repository** — Every merged PR with title, URL, labels, and author +4. **Contributor acknowledgement** — Auto-generated from PR authors (bots and Copilot excluded) ### Example Output @@ -49,16 +50,24 @@ The template is written to `comment-template.md` (default) and contains: --- +## 📦 Releases + +| Repository | Release | Date | +|------------|---------|------| +| lecture-python.myst | [publish-2026feb19b](https://github.com/.../publish-2026feb19b) | Feb 19 | +| quantecon-book-theme | [v0.17.0](https://github.com/.../v0.17.0) | Feb 19 | +| QuantEcon.py | [v0.11.0](https://github.com/.../v0.11.0) | Feb 15 | + **lecture-python.myst:** - - Update consumer problem lecture https://github.com/QuantEcon/lecture-python.myst/pull/757 [maintenance] (@HumphreyYang) - - Fix typo in GDP lecture https://github.com/QuantEcon/lecture-python.myst/pull/756 (@contributor) + - [Update consumer problem lecture](https://github.com/QuantEcon/lecture-python.myst/pull/757) [maintenance] (@HumphreyYang) + - [Fix typo in GDP lecture](https://github.com/QuantEcon/lecture-python.myst/pull/756) (@contributor) **quantecon-book-theme:** - - Major refactor for responsiveness https://github.com/QuantEcon/quantecon-book-theme/pull/335 [infrastructure, priority: high] (@mmcky) + - [Major refactor for responsiveness](https://github.com/QuantEcon/quantecon-book-theme/pull/335) [infrastructure, priority: high] (@mmcky) --- -thanks @contributor1 and @contributor2 for all your contributions. +thanks @HumphreyYang, @mmcky, and @contributor for all your contributions. ``` ## How Labels Help diff --git a/generate-report.sh b/generate-report.sh index 8fe27d2..357a36d 100755 --- a/generate-report.sh +++ b/generate-report.sh @@ -366,6 +366,31 @@ process_external_repo() { local commits=0 commits=$(echo "$commits_response" | jq 'if type == "array" then length else 0 end' 2>/dev/null || echo 0) + # Count releases published in the date range + local releases_response + releases_response=$(api_call "/repos/${full_repo}/releases") + local releases=0 + releases=$(echo "$releases_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false)] | length else 0 end' 2>/dev/null || echo 0) + + # Collect release details for summary template + local local_ext_release_details="" + if [ "$SUMMARY_TEMPLATE" = "true" ] && [ "$releases" -gt 0 ]; then + local_ext_release_details=$(echo "$releases_response" | jq -c --arg since "$WEEK_AGO" --arg until "$NOW" --arg repo "$full_repo" --arg org "$repo_org" ' + if type == "array" then + [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false) | + { + repo: $repo, + org: $org, + tag: .tag_name, + name: (.name // .tag_name), + url: .html_url, + published_at: .published_at, + body: (.body // "" | split("\n")[0:3] | join(" ")) + }] | .[] + else empty end + ' 2>/dev/null) + fi + # Handle null/empty values current_issues=${current_issues:-0} opened_issues=${opened_issues:-0} @@ -373,14 +398,19 @@ process_external_repo() { merged_prs=${merged_prs:-0} opened_prs=${opened_prs:-0} commits=${commits:-0} + releases=${releases:-0} # Return values as JSON for easy parsing - # Include PR details as a nested array for summary template + # Include PR details and release details as nested arrays for summary template local pr_details_json="[]" if [ -n "$local_ext_pr_details" ]; then pr_details_json=$(echo "$local_ext_pr_details" | jq -s '.') fi - echo "{\"repo\":\"$full_repo\",\"current_issues\":$current_issues,\"opened_issues\":$opened_issues,\"closed_issues\":$closed_issues,\"opened_prs\":$opened_prs,\"merged_prs\":$merged_prs,\"commits\":$commits,\"pr_details\":$pr_details_json}" + local release_details_json="[]" + if [ -n "$local_ext_release_details" ]; then + release_details_json=$(echo "$local_ext_release_details" | jq -s '.') + fi + echo "{\"repo\":\"$full_repo\",\"current_issues\":$current_issues,\"opened_issues\":$opened_issues,\"closed_issues\":$closed_issues,\"opened_prs\":$opened_prs,\"merged_prs\":$merged_prs,\"releases\":$releases,\"commits\":$commits,\"pr_details\":$pr_details_json,\"release_details\":$release_details_json}" } # Get all repositories and filter by activity @@ -541,6 +571,7 @@ total_closed_issues=0 total_merged_prs=0 total_commits=0 total_opened_prs=0 +total_releases=0 failed_repos=0 rate_limited_repos=0 report_content="" @@ -570,14 +601,15 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then ## Summary -| Repository | Current Issues | Opened Issues | Closed Issues | Opened PRs | Merged PRs | Commits | -|------------|----------------|---------------|---------------|------------|------------|---------|" +| Repository | Current Issues | Opened Issues | Closed Issues | Opened PRs | Merged PRs | Releases | Commits | +|------------|----------------|---------------|---------------|------------|------------|----------|---------|" echo "DEBUG: Initial report content set, length: ${#report_content}" fi # Initialize summary template data collection -# We collect merged PR details as newline-delimited JSON records +# We collect merged PR details and release info as newline-delimited JSON records summary_template_data="" +summary_release_data="" # Process each repository repo_count=0 @@ -693,6 +725,45 @@ ${local_pr_details}" fi fi + # Count releases published in the date range + releases_response=$(api_call "/repos/${ORGANIZATION}/${repo}/releases") + if [ $? -eq 0 ]; then + releases=$(echo "$releases_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false)] | length else 0 end') + + # Collect release details for summary template + if [ "$SUMMARY_TEMPLATE" = "true" ] && [ "$releases" -gt 0 ]; then + local_release_details=$(echo "$releases_response" | jq -c --arg since "$WEEK_AGO" --arg until "$NOW" --arg repo "$repo" --arg org "$ORGANIZATION" ' + if type == "array" then + [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false) | + { + repo: $repo, + org: $org, + tag: .tag_name, + name: (.name // .tag_name), + url: .html_url, + published_at: .published_at, + body: (.body // "" | split("\n")[0:3] | join(" ")) + }] | .[] + else empty end + ' 2>/dev/null) + if [ -n "$local_release_details" ]; then + if [ -n "$summary_release_data" ]; then + summary_release_data="${summary_release_data} +${local_release_details}" + else + summary_release_data="$local_release_details" + fi + fi + fi + else + releases=0 + if echo "$releases_response" | jq -e '.error' 2>/dev/null | grep -q "rate_limit"; then + rate_limited_repos=$((rate_limited_repos + 1)) + else + failed_repos=$((failed_repos + 1)) + fi + fi + # Handle null/empty values current_issues=${current_issues:-0} opened_issues=${opened_issues:-0} @@ -700,6 +771,7 @@ ${local_pr_details}" merged_prs=${merged_prs:-0} opened_prs=${opened_prs:-0} commits=${commits:-0} + releases=${releases:-0} # Add to totals total_current_issues=$((total_current_issues + current_issues)) @@ -708,10 +780,11 @@ ${local_pr_details}" total_merged_prs=$((total_merged_prs + merged_prs)) total_opened_prs=$((total_opened_prs + opened_prs)) total_commits=$((total_commits + commits)) + total_releases=$((total_releases + releases)) # Only include repos with actual activity (exclude repos with all zeros in activity columns) # Note: We check activity metrics only, not current_issues (which is current state, not activity) - if [ $((opened_issues + closed_issues + opened_prs + merged_prs + commits)) -gt 0 ]; then + if [ $((opened_issues + closed_issues + opened_prs + merged_prs + commits + releases)) -gt 0 ]; then repos_with_activity=$((repos_with_activity + 1)) if [ "$OUTPUT_FORMAT" = "markdown" ]; then # Create GitHub search URLs for clickable metrics @@ -721,6 +794,7 @@ ${local_pr_details}" closed_issues_url="https://github.com/${ORGANIZATION}/${repo}/issues?q=is:issue+closed:${start_display}..${end_display}" opened_prs_url="https://github.com/${ORGANIZATION}/${repo}/pulls?q=is:pr+created:${start_display}..${end_display}" merged_prs_url="https://github.com/${ORGANIZATION}/${repo}/pulls?q=is:pr+merged:${start_display}..${end_display}" + releases_url="https://github.com/${ORGANIZATION}/${repo}/releases" commits_url="https://github.com/${ORGANIZATION}/${repo}/commits?since=${start_display}&until=${end_display}" # Format metrics as clickable links if > 1 @@ -729,10 +803,11 @@ ${local_pr_details}" closed_issues_display=$(format_metric "$closed_issues" "$closed_issues_url") opened_prs_display=$(format_metric "$opened_prs" "$opened_prs_url") merged_prs_display=$(format_metric "$merged_prs" "$merged_prs_url") + releases_display=$(format_metric "$releases" "$releases_url") commits_display=$(format_metric "$commits" "$commits_url") report_content="${report_content} -| $repo | $current_issues_display | $opened_issues_display | $closed_issues_display | $opened_prs_display | $merged_prs_display | $commits_display |" +| $repo | $current_issues_display | $opened_issues_display | $closed_issues_display | $opened_prs_display | $merged_prs_display | $releases_display | $commits_display |" fi else echo "DEBUG: Skipping $repo from report (no activity: all metrics are zero)" @@ -748,7 +823,7 @@ echo "DEBUG: Final report content length: ${#report_content}" # Add summary to report if [ "$OUTPUT_FORMAT" = "markdown" ]; then report_content="${report_content} -|**Total**|**$total_current_issues**|**$total_opened_issues**|**$total_closed_issues**|**$total_opened_prs**|**$total_merged_prs**|**$total_commits**| +|**Total**|**$total_current_issues**|**$total_opened_issues**|**$total_closed_issues**|**$total_opened_prs**|**$total_merged_prs**|**$total_releases**|**$total_commits**| ## Details @@ -758,6 +833,7 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then - **Total Issues Closed:** $total_closed_issues - **Total PRs Opened:** $total_opened_prs - **Total PRs Merged:** $total_merged_prs +- **Total Releases:** $total_releases - **Total Commits:** $total_commits" # Add warnings about incomplete data if any API calls failed @@ -791,6 +867,7 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then ext_total_merged_prs=0 ext_total_commits=0 ext_total_opened_prs=0 + ext_total_releases=0 ext_repos_with_activity=0 ext_report_content="" @@ -809,6 +886,7 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then closed_issues=$(echo "$repo_result" | jq -r '.closed_issues') opened_prs=$(echo "$repo_result" | jq -r '.opened_prs') merged_prs=$(echo "$repo_result" | jq -r '.merged_prs') + releases=$(echo "$repo_result" | jq -r '.releases') commits=$(echo "$repo_result" | jq -r '.commits') # Extract PR details for summary template @@ -822,6 +900,17 @@ ${ext_pr_details}" summary_template_data="$ext_pr_details" fi fi + + # Extract release details for summary template + ext_release_details=$(echo "$repo_result" | jq -c '.release_details[]?' 2>/dev/null) + if [ -n "$ext_release_details" ]; then + if [ -n "$summary_release_data" ]; then + summary_release_data="${summary_release_data} +${ext_release_details}" + else + summary_release_data="$ext_release_details" + fi + fi fi # Add to totals @@ -831,9 +920,10 @@ ${ext_pr_details}" ext_total_merged_prs=$((ext_total_merged_prs + merged_prs)) ext_total_opened_prs=$((ext_total_opened_prs + opened_prs)) ext_total_commits=$((ext_total_commits + commits)) + ext_total_releases=$((ext_total_releases + releases)) # Only include repos with activity - if [ $((opened_issues + closed_issues + opened_prs + merged_prs + commits)) -gt 0 ]; then + if [ $((opened_issues + closed_issues + opened_prs + merged_prs + commits + releases)) -gt 0 ]; then ext_repos_with_activity=$((ext_repos_with_activity + 1)) # Create GitHub search URLs @@ -842,6 +932,7 @@ ${ext_pr_details}" closed_issues_url="https://github.com/${full_repo}/issues?q=is:issue+closed:${start_display}..${end_display}" opened_prs_url="https://github.com/${full_repo}/pulls?q=is:pr+created:${start_display}..${end_display}" merged_prs_url="https://github.com/${full_repo}/pulls?q=is:pr+merged:${start_display}..${end_display}" + releases_url="https://github.com/${full_repo}/releases" commits_url="https://github.com/${full_repo}/commits?since=${start_display}&until=${end_display}" # Format metrics as clickable links if > 0 @@ -850,10 +941,11 @@ ${ext_pr_details}" closed_issues_display=$(format_metric "$closed_issues" "$closed_issues_url") opened_prs_display=$(format_metric "$opened_prs" "$opened_prs_url") merged_prs_display=$(format_metric "$merged_prs" "$merged_prs_url") + releases_display=$(format_metric "$releases" "$releases_url") commits_display=$(format_metric "$commits" "$commits_url") ext_report_content="${ext_report_content} -| $full_repo | $current_issues_display | $opened_issues_display | $closed_issues_display | $opened_prs_display | $merged_prs_display | $commits_display |" +| $full_repo | $current_issues_display | $opened_issues_display | $closed_issues_display | $opened_prs_display | $merged_prs_display | $releases_display | $commits_display |" else echo "DEBUG: Skipping $full_repo from report (no activity: all metrics are zero)" fi @@ -865,9 +957,9 @@ ${ext_pr_details}" ## External Repositories -| Repository | Current Issues | Opened Issues | Closed Issues | Opened PRs | Merged PRs | Commits | -|------------|----------------|---------------|---------------|------------|------------|---------|${ext_report_content} -|**Total**|**$ext_total_current_issues**|**$ext_total_opened_issues**|**$ext_total_closed_issues**|**$ext_total_opened_prs**|**$ext_total_merged_prs**|**$ext_total_commits**|" +| Repository | Current Issues | Opened Issues | Closed Issues | Opened PRs | Merged PRs | Releases | Commits | +|------------|----------------|---------------|---------------|------------|------------|----------|---------|${ext_report_content} +|**Total**|**$ext_total_current_issues**|**$ext_total_opened_issues**|**$ext_total_closed_issues**|**$ext_total_opened_prs**|**$ext_total_merged_prs**|**$ext_total_releases**|**$ext_total_commits**|" echo "DEBUG: External repositories with activity: $ext_repos_with_activity" echo "DEBUG: External repos totals - Issues: $ext_total_opened_issues opened, $ext_total_closed_issues closed | PRs: $ext_total_opened_prs opened, $ext_total_merged_prs merged | Commits: $ext_total_commits" @@ -882,7 +974,7 @@ ${ext_pr_details}" fi # Create summary -summary="Summary: $total_current_issues current open issues, $total_opened_issues issues opened, $total_closed_issues issues closed, $total_opened_prs PRs opened, $total_merged_prs PRs merged, $total_commits commits" +summary="Summary: $total_current_issues current open issues, $total_opened_issues issues opened, $total_closed_issues issues closed, $total_opened_prs PRs opened, $total_merged_prs PRs merged, $total_releases releases, $total_commits commits" # Generate comment summary template if enabled if [ "$SUMMARY_TEMPLATE" = "true" ]; then @@ -902,9 +994,41 @@ if [ "$SUMMARY_TEMPLATE" = "true" ]; then ---" - # Group PRs by repository and generate sections + # --- Releases table (Option A: single table at top, omitted if no releases) --- + if [ -n "$summary_release_data" ]; then + template_content="${template_content} + +## 📦 Releases + +| Repository | Release | Date | +|------------|---------|------|" + + # Sort all releases by published_at descending + sorted_releases=$(echo "$summary_release_data" | jq -s 'sort_by(.published_at) | reverse | .[]' -c 2>/dev/null) + + while IFS= read -r release_json; do + [ -z "$release_json" ] && continue + release_repo=$(echo "$release_json" | jq -r '.repo') + release_name=$(echo "$release_json" | jq -r '.name') + release_tag=$(echo "$release_json" | jq -r '.tag') + release_url=$(echo "$release_json" | jq -r '.url') + release_date=$(echo "$release_json" | jq -r '.published_at' | cut -d'T' -f1) + # Format the date as "Feb 21" style (GNU date -d first, then BSD date -j -f, then fallback) + release_date_display=$(date -d "$release_date" "+%b %d" 2>/dev/null || date -j -f "%Y-%m-%d" "$release_date" "+%b %d" 2>/dev/null || echo "$release_date") + # Use name if different from tag, otherwise just show tag + if [ "$release_name" != "$release_tag" ] && [ -n "$release_name" ]; then + release_label="${release_name} (${release_tag})" + else + release_label="${release_tag}" + fi + template_content="${template_content} +| ${release_repo} | [${release_label}](${release_url}) | ${release_date_display} |" + done <<< "$sorted_releases" + fi + + # --- PR listings per repository --- if [ -n "$summary_template_data" ]; then - # Get unique repos from the collected data + # Get unique repos from the collected PR data repo_list=$(echo "$summary_template_data" | jq -r '.repo' | sort -u) while IFS= read -r template_repo; do @@ -915,7 +1039,7 @@ if [ "$SUMMARY_TEMPLATE" = "true" ]; then **${template_repo}:**" # Get PRs for this repo, sorted by merged_at - repo_prs=$(echo "$summary_template_data" | jq -c --arg repo "$template_repo" 'select(.repo == $repo)' | jq -s 'sort_by(.merged_at) | reverse | .[]' -c) + repo_prs=$(echo "$summary_template_data" | jq -c --arg repo "$template_repo" 'select(.repo == $repo)' | jq -s 'sort_by(.merged_at) | reverse | .[]' -c 2>/dev/null) while IFS= read -r pr_json; do [ -z "$pr_json" ] && continue @@ -929,10 +1053,11 @@ if [ "$SUMMARY_TEMPLATE" = "true" ]; then done <<< "$repo_prs" done <<< "$repo_list" - else + elif [ -z "$summary_release_data" ]; then + # No PRs and no releases template_content="${template_content} -_No merged PRs found in the report period._" +_No merged PRs or releases found in the report period._" fi # Build contributors line from actual PR authors From 326be54bdb071a00008330a7358bc0519f4c4a79 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 16:54:18 +1100 Subject: [PATCH 5/8] fix: replace unsafe shell interpolation example with safe patterns Use env: variable and action with: input instead of direct ${{ }} interpolation in run: blocks to prevent shell injection from PR-derived content (titles, labels, author names). --- docs/comment-summary-template.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/comment-summary-template.md b/docs/comment-summary-template.md index 1bfe0e8..4c557f1 100644 --- a/docs/comment-summary-template.md +++ b/docs/comment-summary-template.md @@ -135,10 +135,25 @@ The template content is also available as the `comment-template` output for use github-token: ${{ secrets.GITHUB_TOKEN }} summary-template: 'true' +# Option 1: Pass to an action input (recommended — inherently safe) +- name: Post comment + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: 1 + body: ${{ steps.report.outputs.comment-template }} + +# Option 2: Use via environment variable (safe for run steps) - name: Use template content - run: echo "${{ steps.report.outputs.comment-template }}" + env: + TEMPLATE: ${{ steps.report.outputs.comment-template }} + run: echo "$TEMPLATE" ``` +> **Security note:** Never interpolate `${{ steps.report.outputs.comment-template }}` +> directly in a `run:` block. The output contains PR titles, labels, and author names +> which could include shell metacharacters. Always pass it through an `env:` variable +> or an action `with:` input to avoid shell injection. + ## Inputs | Input | Description | Default | From a9a8c09030ccd9cfad82cf93573b733fefa0af8f Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 17:02:44 +1100 Subject: [PATCH 6/8] fix: remove no-op shift statements and add trailing newline to action.yml --- action.yml | 2 +- generate-report.sh | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/action.yml b/action.yml index 56d49b9..c0a8c19 100644 --- a/action.yml +++ b/action.yml @@ -61,4 +61,4 @@ runs: INPUT_TRACK_EXTERNAL_REPOS: ${{ inputs.track-external-repos }} INPUT_API_DELAY: ${{ inputs.api-delay }} INPUT_SUMMARY_TEMPLATE: ${{ inputs.summary-template }} - INPUT_SUMMARY_TEMPLATE_FILE: ${{ inputs.summary-template-file }} \ No newline at end of file + INPUT_SUMMARY_TEMPLATE_FILE: ${{ inputs.summary-template-file }} diff --git a/generate-report.sh b/generate-report.sh index 70b7c88..4119b5b 100755 --- a/generate-report.sh +++ b/generate-report.sh @@ -105,11 +105,9 @@ for arg in "$@"; do ;; --summary-template) CLI_SUMMARY_TEMPLATE="true" - shift ;; --summary-template-file=*) CLI_SUMMARY_TEMPLATE_FILE="${arg#*=}" - shift ;; --help) show_usage From a15bffdc9f20e37e6de76beec33457d3a5e54ed5 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 17:07:08 +1100 Subject: [PATCH 7/8] feat: gate debug output behind --debug flag - Add --debug CLI flag and debug action input (default: false) - Add debug() function that only prints when DEBUG_MODE=true - Replace all 26 'echo DEBUG:' calls with debug() function - Update README, action.yml, and CHANGELOG --- CHANGELOG.md | 1 + README.md | 2 ++ action.yml | 5 ++++ generate-report.sh | 67 ++++++++++++++++++++++++++++------------------ 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a528a..3a1f83a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Argument parsing**: Removed no-op `shift` statements inside `for arg in "$@"` loop in `generate-report.sh` (shift has no effect inside a for loop) - **Copilot instructions**: Added `.tmp/` file staging guidelines to prevent heredoc/shell escaping issues - **`.gitignore`**: Added `.tmp/` working directory for file staging +- **Debug output**: All debug messages are now gated behind `--debug` CLI flag / `debug` action input (silent by default) ## [2.2.0] - 2025-10-23 diff --git a/README.md b/README.md index 515a101..9653410 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ cat report.md - `--delay=SECONDS` - Delay between API calls (default: 0) - `--summary-template` - Generate a comment summary template with merged PR details - `--summary-template-file=FILE` - Output filename for summary template (default: `comment-template.md`) +- `--debug` - Enable verbose debug output The report is saved to `report.md` (or your specified output file) in the current directory. @@ -129,6 +130,7 @@ The report is saved to `report.md` (or your specified output file) in the curren | `api-delay` | Delay in seconds between API calls to avoid rate limits (0 = no delay) | No | `0` | | `summary-template` | Generate a comment summary template with merged PR details (`true`/`false`) | No | `false` | | `summary-template-file` | Output filename for the comment summary template | No | `comment-template.md` | +| `debug` | Enable verbose debug output (`true`/`false`) | No | `false` | ## Outputs diff --git a/action.yml b/action.yml index c0a8c19..fdeb6d6 100644 --- a/action.yml +++ b/action.yml @@ -34,6 +34,10 @@ inputs: description: 'Output filename for the comment summary template' required: false default: 'comment-template.md' + debug: + description: 'Enable verbose debug output (true/false)' + required: false + default: 'false' outputs: report-content: @@ -62,3 +66,4 @@ runs: INPUT_API_DELAY: ${{ inputs.api-delay }} INPUT_SUMMARY_TEMPLATE: ${{ inputs.summary-template }} INPUT_SUMMARY_TEMPLATE_FILE: ${{ inputs.summary-template-file }} + INPUT_DEBUG: ${{ inputs.debug }} diff --git a/generate-report.sh b/generate-report.sh index 4119b5b..0ad373c 100755 --- a/generate-report.sh +++ b/generate-report.sh @@ -9,6 +9,7 @@ CLI_ORG="" CLI_OUTPUT="" CLI_SUMMARY_TEMPLATE="" CLI_SUMMARY_TEMPLATE_FILE="" +CLI_DEBUG="" show_usage() { cat << EOF @@ -27,6 +28,7 @@ OPTIONS: --delay=SECONDS Delay between API calls (default: 0) --summary-template Generate a comment summary template with merged PR details --summary-template-file=FILE Output filename for summary template (default: comment-template.md) + --debug Enable verbose debug output --help Show this help message EXAMPLES: @@ -69,6 +71,7 @@ ENVIRONMENT VARIABLES: INPUT_API_DELAY API delay in seconds INPUT_SUMMARY_TEMPLATE Enable summary template generation (true/false) INPUT_SUMMARY_TEMPLATE_FILE Summary template output filename + INPUT_DEBUG Enable debug output (true/false) OUTPUT: Report is saved to: report.md (or specified --output file) @@ -109,6 +112,9 @@ for arg in "$@"; do --summary-template-file=*) CLI_SUMMARY_TEMPLATE_FILE="${arg#*=}" ;; + --debug) + CLI_DEBUG="true" + ;; --help) show_usage exit 0 @@ -121,8 +127,17 @@ for arg in "$@"; do esac done -echo "DEBUG: Starting report generation" -echo "DEBUG: Environment check - GITHUB_OUTPUT: ${GITHUB_OUTPUT:-NOT_SET}" +# Resolve debug flag early so debug() function works +DEBUG_MODE="${CLI_DEBUG:-${INPUT_DEBUG:-false}}" + +debug() { + if [ "$DEBUG_MODE" = "true" ]; then + echo "DEBUG: $*" + fi +} + +debug "Starting report generation" +debug "Environment check - GITHUB_OUTPUT: ${GITHUB_OUTPUT:-NOT_SET}" # Get inputs - CLI args take precedence over environment variables GITHUB_TOKEN="${CLI_TOKEN:-${GITHUB_TOKEN:-${INPUT_GITHUB_TOKEN}}}" @@ -158,10 +173,10 @@ if [ -z "$GITHUB_TOKEN" ]; then exit 1 fi -echo "DEBUG: Inputs - ORG: $ORGANIZATION, FORMAT: $OUTPUT_FORMAT, EXCLUDE: $EXCLUDE_REPOS" -echo "DEBUG: External repos: $TRACK_EXTERNAL_REPOS" -echo "DEBUG: Output file: $OUTPUT_FILE" -echo "DEBUG: Summary template: $SUMMARY_TEMPLATE (file: $SUMMARY_TEMPLATE_FILE)" +debug "Inputs - ORG: $ORGANIZATION, FORMAT: $OUTPUT_FORMAT, EXCLUDE: $EXCLUDE_REPOS" +debug "External repos: $TRACK_EXTERNAL_REPOS" +debug "Output file: $OUTPUT_FILE" +debug "Summary template: $SUMMARY_TEMPLATE (file: $SUMMARY_TEMPLATE_FILE)" # Validate external repos format (must be org/repo) if [ -n "$TRACK_EXTERNAL_REPOS" ]; then @@ -593,7 +608,7 @@ if [ "$OUTPUT_FORMAT" = "markdown" ]; then | Repository | Current Issues | Opened Issues | Closed Issues | Opened PRs | Merged PRs | Releases | Commits | |------------|----------------|---------------|---------------|------------|------------|----------|---------|" - echo "DEBUG: Initial report content set, length: ${#report_content}" + debug "Initial report content set, length: ${#report_content}" fi # Initialize summary template data collection @@ -800,15 +815,15 @@ ${local_release_details}" | $repo | $current_issues_display | $opened_issues_display | $closed_issues_display | $opened_prs_display | $merged_prs_display | $releases_display | $commits_display |" fi else - echo "DEBUG: Skipping $repo from report (no activity: all metrics are zero)" + debug "Skipping $repo from report (no activity: all metrics are zero)" fi done <<< "$repo_names" -echo "DEBUG: Processed $repo_count repositories" -echo "DEBUG: Repositories with activity (included in report): $repos_with_activity" -echo "DEBUG: Repositories skipped (no activity): $((repo_count - repos_with_activity))" -echo "DEBUG: Final report content length: ${#report_content}" +debug "Processed $repo_count repositories" +debug "Repositories with activity (included in report): $repos_with_activity" +debug "Repositories skipped (no activity): $((repo_count - repos_with_activity))" +debug "Final report content length: ${#report_content}" # Add summary to report if [ "$OUTPUT_FORMAT" = "markdown" ]; then @@ -937,7 +952,7 @@ ${ext_release_details}" ext_report_content="${ext_report_content} | $full_repo | $current_issues_display | $opened_issues_display | $closed_issues_display | $opened_prs_display | $merged_prs_display | $releases_display | $commits_display |" else - echo "DEBUG: Skipping $full_repo from report (no activity: all metrics are zero)" + debug "Skipping $full_repo from report (no activity: all metrics are zero)" fi done @@ -951,10 +966,10 @@ ${ext_release_details}" |------------|----------------|---------------|---------------|------------|------------|----------|---------|${ext_report_content} |**Total**|**$ext_total_current_issues**|**$ext_total_opened_issues**|**$ext_total_closed_issues**|**$ext_total_opened_prs**|**$ext_total_merged_prs**|**$ext_total_releases**|**$ext_total_commits**|" - echo "DEBUG: External repositories with activity: $ext_repos_with_activity" - echo "DEBUG: External repos totals - Issues: $ext_total_opened_issues opened, $ext_total_closed_issues closed | PRs: $ext_total_opened_prs opened, $ext_total_merged_prs merged | Commits: $ext_total_commits" + debug "External repositories with activity: $ext_repos_with_activity" + debug "External repos totals - Issues: $ext_total_opened_issues opened, $ext_total_closed_issues closed | PRs: $ext_total_opened_prs opened, $ext_total_merged_prs merged | Commits: $ext_total_commits" else - echo "DEBUG: No external repository activity in date range" + debug "No external repository activity in date range" fi fi @@ -1089,7 +1104,7 @@ ${contributors_line}" # Save template to file echo "$template_content" > "$SUMMARY_TEMPLATE_FILE" echo "Comment summary template generated: $SUMMARY_TEMPLATE_FILE" - echo "DEBUG: Summary template length: ${#template_content}" + debug "Summary template length: ${#template_content}" fi # Save report to file @@ -1098,15 +1113,15 @@ echo "$report_content" > "$OUTPUT_FILE" echo "Report generated: $OUTPUT_FILE" # Debug: Check if GITHUB_OUTPUT is set and accessible -echo "DEBUG: GITHUB_OUTPUT environment variable: ${GITHUB_OUTPUT:-NOT_SET}" -echo "DEBUG: Report content length: ${#report_content}" -echo "DEBUG: Summary: $summary" +debug "GITHUB_OUTPUT environment variable: ${GITHUB_OUTPUT:-NOT_SET}" +debug "Report content length: ${#report_content}" +debug "Summary: $summary" # Set outputs if [ -n "$GITHUB_OUTPUT" ]; then - echo "DEBUG: Writing to GITHUB_OUTPUT file" - echo "DEBUG: Content preview (first 100 chars): ${report_content:0:100}" - echo "DEBUG: Summary preview: $summary" + debug "Writing to GITHUB_OUTPUT file" + debug "Content preview (first 100 chars): ${report_content:0:100}" + debug "Summary preview: $summary" # Use a unique delimiter to avoid conflicts with content delimiter="QUANTECON_REPORT_END_$(date +%s)" @@ -1124,10 +1139,10 @@ if [ -n "$GITHUB_OUTPUT" ]; then echo "${template_delimiter}" >> "$GITHUB_OUTPUT" fi - echo "DEBUG: Outputs written to GITHUB_OUTPUT" - echo "DEBUG: GITHUB_OUTPUT file size: $(wc -c < "$GITHUB_OUTPUT")" + debug "Outputs written to GITHUB_OUTPUT" + debug "GITHUB_OUTPUT file size: $(wc -c < "$GITHUB_OUTPUT")" else - echo "DEBUG: GITHUB_OUTPUT not set (expected in CLI mode, outputs are for GitHub Actions only)" + debug "GITHUB_OUTPUT not set (expected in CLI mode, outputs are for GitHub Actions only)" fi echo "Report generated successfully!" From f25d5eb539da107ac8646f201ef2e49f60f0a67b Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 23 Feb 2026 19:17:36 +1100 Subject: [PATCH 8/8] fix: address set -e API call safety, date formatting, and docs accuracy - Restructure all api_call assignments to if-guard pattern so set -e doesn't abort the script on API failures (both org and external repos) - Use non-zero-padded day format in releases table (%-d GNU, %e BSD) - Fix comment saying '> 1' to match format_metric() behavior of '> 0' - Soften docs wording from 'all merged PRs' to 'merged PRs' - Document 100-item-per-page API limit in comment template docs --- CHANGELOG.md | 4 +++ docs/comment-summary-template.md | 8 +++-- generate-report.sh | 62 ++++++++++++++++---------------- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a1f83a..013a2fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Corrupted tests README**: Rewrote `tests/README.md` to remove duplicated content and ensure it only references existing test files - **CLI error message**: Changed misleading `ERROR: GITHUB_OUTPUT environment variable not set!` to a debug message in CLI mode (GITHUB_OUTPUT is only relevant in GitHub Actions) - **Hyperlinks documentation**: Fixed `docs/hyperlinks.md` overview that incorrectly said "metrics greater than 1" when the actual behavior is "metrics greater than 0" +- **`set -e` safety for API calls**: Restructured all `api_call` assignments to use `if var=$(api_call ...); then` pattern so that API failures are handled gracefully instead of aborting the script +- **Date formatting in releases table**: Use non-zero-padded day format (`Feb 5` instead of `Feb 05`) for consistent display with documentation examples +- **Clickable links comment**: Fixed comment that said "if > 1" to match actual `format_metric()` behavior of linking when count is > 0 ### Changed - **CONTRIBUTING.md**: Updated from generic Python template (referenced `pip install`, `pytest`, PEP 8) to accurate shell-project instructions with correct tooling (`bash`, `curl`, `jq`) and test commands @@ -43,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Copilot instructions**: Added `.tmp/` file staging guidelines to prevent heredoc/shell escaping issues - **`.gitignore`**: Added `.tmp/` working directory for file staging - **Debug output**: All debug messages are now gated behind `--debug` CLI flag / `debug` action input (silent by default) +- **Comment template docs**: Softened wording from "all merged PRs" to "merged PRs" and documented the 100-item-per-page API limit ## [2.2.0] - 2025-10-23 diff --git a/docs/comment-summary-template.md b/docs/comment-summary-template.md index 4c557f1..0e28147 100644 --- a/docs/comment-summary-template.md +++ b/docs/comment-summary-template.md @@ -1,6 +1,8 @@ # Comment Summary Template -The comment summary template feature auto-generates an editable markdown template containing all merged PR details from the report period. This helps write the "highlights" comment that accompanies each activity report. +The comment summary template feature auto-generates an editable markdown template containing merged PR details from the report period. This helps write the "highlights" comment that accompanies each activity report. + +> **Note:** The template includes up to 100 merged PRs and 100 releases per repository (the GitHub API default page size). For most repositories and weekly report windows, this covers all activity. ## Background @@ -37,7 +39,7 @@ The template is written to `comment-template.md` (default) and contains: 1. **Placeholder summary** — Editable topic bullets for high-level themes 2. **Releases table** — All releases published in the period in a single table (omitted if none) -3. **Merged PRs by repository** — Every merged PR with title, URL, labels, and author +3. **Merged PRs by repository** — Merged PRs with title, URL, labels, and author 4. **Contributor acknowledgement** — Auto-generated from PR authors (bots and Copilot excluded) ### Example Output @@ -117,7 +119,7 @@ The template lands as the first comment on the new issue. A maintainer then edit ### Manual 1. Run the action or CLI with `summary-template` enabled -2. Open `comment-template.md` — all merged PRs are listed by repo +2. Open `comment-template.md` — merged PRs are listed by repo 3. Reorganize into thematic sections (Lectures, Infrastructure, Libraries, etc.) 4. Add high-level context for each section 5. Remove PRs that don't need highlighting (e.g., automated dependency bumps) diff --git a/generate-report.sh b/generate-report.sh index 0ad373c..0662d08 100755 --- a/generate-report.sh +++ b/generate-report.sh @@ -317,27 +317,31 @@ process_external_repo() { # Count total current open issues local current_issues_response - current_issues_response=$(api_call "/repos/${full_repo}/issues?state=open") local current_issues=0 - current_issues=$(echo "$current_issues_response" | jq 'if type == "array" then [.[] | select(.pull_request == null)] | length else 0 end' 2>/dev/null || echo 0) + if current_issues_response=$(api_call "/repos/${full_repo}/issues?state=open"); then + current_issues=$(echo "$current_issues_response" | jq 'if type == "array" then [.[] | select(.pull_request == null)] | length else 0 end' 2>/dev/null || echo 0) + fi # Count opened issues in the date range local opened_response - opened_response=$(api_call "/repos/${full_repo}/issues?state=all") local opened_issues=0 - opened_issues=$(echo "$opened_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.created_at >= $since and .created_at <= $until and .pull_request == null)] | length else 0 end' 2>/dev/null || echo 0) + if opened_response=$(api_call "/repos/${full_repo}/issues?state=all"); then + opened_issues=$(echo "$opened_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.created_at >= $since and .created_at <= $until and .pull_request == null)] | length else 0 end' 2>/dev/null || echo 0) + fi # Count closed issues in the date range local closed_response - closed_response=$(api_call "/repos/${full_repo}/issues?state=closed") local closed_issues=0 - closed_issues=$(echo "$closed_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.closed_at != null and .closed_at >= $since and .closed_at <= $until and .pull_request == null)] | length else 0 end' 2>/dev/null || echo 0) + if closed_response=$(api_call "/repos/${full_repo}/issues?state=closed"); then + closed_issues=$(echo "$closed_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.closed_at != null and .closed_at >= $since and .closed_at <= $until and .pull_request == null)] | length else 0 end' 2>/dev/null || echo 0) + fi # Count merged PRs in the date range local prs_response - prs_response=$(api_call "/repos/${full_repo}/pulls?state=closed") local merged_prs=0 - merged_prs=$(echo "$prs_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.merged_at != null and .merged_at >= $since and .merged_at <= $until)] | length else 0 end' 2>/dev/null || echo 0) + if prs_response=$(api_call "/repos/${full_repo}/pulls?state=closed"); then + merged_prs=$(echo "$prs_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.merged_at != null and .merged_at >= $since and .merged_at <= $until)] | length else 0 end' 2>/dev/null || echo 0) + fi # Collect merged PR details for summary template local local_ext_pr_details="" @@ -361,21 +365,24 @@ process_external_repo() { # Count opened PRs in the date range local all_prs_response - all_prs_response=$(api_call "/repos/${full_repo}/pulls?state=all") local opened_prs=0 - opened_prs=$(echo "$all_prs_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.created_at >= $since and .created_at <= $until)] | length else 0 end' 2>/dev/null || echo 0) + if all_prs_response=$(api_call "/repos/${full_repo}/pulls?state=all"); then + opened_prs=$(echo "$all_prs_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.created_at >= $since and .created_at <= $until)] | length else 0 end' 2>/dev/null || echo 0) + fi # Count commits in the date range local commits_response - commits_response=$(api_call "/repos/${full_repo}/commits?since=${WEEK_AGO}&until=${NOW}") local commits=0 - commits=$(echo "$commits_response" | jq 'if type == "array" then length else 0 end' 2>/dev/null || echo 0) + if commits_response=$(api_call "/repos/${full_repo}/commits?since=${WEEK_AGO}&until=${NOW}"); then + commits=$(echo "$commits_response" | jq 'if type == "array" then length else 0 end' 2>/dev/null || echo 0) + fi # Count releases published in the date range local releases_response - releases_response=$(api_call "/repos/${full_repo}/releases") local releases=0 - releases=$(echo "$releases_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false)] | length else 0 end' 2>/dev/null || echo 0) + if releases_response=$(api_call "/repos/${full_repo}/releases"); then + releases=$(echo "$releases_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false)] | length else 0 end' 2>/dev/null || echo 0) + fi # Collect release details for summary template local local_ext_release_details="" @@ -626,8 +633,7 @@ while IFS= read -r repo; do echo "Processing repository: $repo" # Count total current open issues - current_issues_response=$(api_call "/repos/${ORGANIZATION}/${repo}/issues?state=open") - if [ $? -eq 0 ]; then + if current_issues_response=$(api_call "/repos/${ORGANIZATION}/${repo}/issues?state=open"); then current_issues=$(echo "$current_issues_response" | jq 'if type == "array" then [.[] | select(.pull_request == null)] | length else 0 end') else current_issues=0 @@ -639,8 +645,7 @@ while IFS= read -r repo; do fi # Count opened issues in the date range - opened_response=$(api_call "/repos/${ORGANIZATION}/${repo}/issues?state=all") - if [ $? -eq 0 ]; then + if opened_response=$(api_call "/repos/${ORGANIZATION}/${repo}/issues?state=all"); then opened_issues=$(echo "$opened_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.created_at >= $since and .created_at <= $until and .pull_request == null)] | length else 0 end') else opened_issues=0 @@ -652,8 +657,7 @@ while IFS= read -r repo; do fi # Count closed issues in the date range - closed_response=$(api_call "/repos/${ORGANIZATION}/${repo}/issues?state=closed") - if [ $? -eq 0 ]; then + if closed_response=$(api_call "/repos/${ORGANIZATION}/${repo}/issues?state=closed"); then closed_issues=$(echo "$closed_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.closed_at != null and .closed_at >= $since and .closed_at <= $until and .pull_request == null)] | length else 0 end') else closed_issues=0 @@ -665,8 +669,7 @@ while IFS= read -r repo; do fi # Count merged PRs in the date range - prs_response=$(api_call "/repos/${ORGANIZATION}/${repo}/pulls?state=closed") - if [ $? -eq 0 ]; then + if prs_response=$(api_call "/repos/${ORGANIZATION}/${repo}/pulls?state=closed"); then merged_prs=$(echo "$prs_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.merged_at != null and .merged_at >= $since and .merged_at <= $until)] | length else 0 end') # Collect merged PR details for summary template @@ -705,8 +708,7 @@ ${local_pr_details}" fi # Count opened PRs in the date range (both open and closed) - all_prs_response=$(api_call "/repos/${ORGANIZATION}/${repo}/pulls?state=all") - if [ $? -eq 0 ]; then + if all_prs_response=$(api_call "/repos/${ORGANIZATION}/${repo}/pulls?state=all"); then opened_prs=$(echo "$all_prs_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.created_at >= $since and .created_at <= $until)] | length else 0 end') else opened_prs=0 @@ -718,8 +720,7 @@ ${local_pr_details}" fi # Count commits in the date range - commits_response=$(api_call "/repos/${ORGANIZATION}/${repo}/commits?since=${WEEK_AGO}&until=${NOW}") - if [ $? -eq 0 ]; then + if commits_response=$(api_call "/repos/${ORGANIZATION}/${repo}/commits?since=${WEEK_AGO}&until=${NOW}"); then commits=$(echo "$commits_response" | jq 'if type == "array" then length else 0 end') else commits=0 @@ -731,8 +732,7 @@ ${local_pr_details}" fi # Count releases published in the date range - releases_response=$(api_call "/repos/${ORGANIZATION}/${repo}/releases") - if [ $? -eq 0 ]; then + if releases_response=$(api_call "/repos/${ORGANIZATION}/${repo}/releases"); then releases=$(echo "$releases_response" | jq --arg since "$WEEK_AGO" --arg until "$NOW" 'if type == "array" then [.[] | select(.published_at != null and .published_at >= $since and .published_at <= $until and .draft == false)] | length else 0 end') # Collect release details for summary template @@ -802,7 +802,7 @@ ${local_release_details}" releases_url="https://github.com/${ORGANIZATION}/${repo}/releases" commits_url="https://github.com/${ORGANIZATION}/${repo}/commits?since=${start_display}&until=${end_display}" - # Format metrics as clickable links if > 1 + # Format metrics as clickable links if > 0 current_issues_display=$(format_metric "$current_issues" "$current_issues_url") opened_issues_display=$(format_metric "$opened_issues" "$opened_issues_url") closed_issues_display=$(format_metric "$closed_issues" "$closed_issues_url") @@ -1018,8 +1018,8 @@ if [ "$SUMMARY_TEMPLATE" = "true" ]; then release_tag=$(echo "$release_json" | jq -r '.tag') release_url=$(echo "$release_json" | jq -r '.url') release_date=$(echo "$release_json" | jq -r '.published_at' | cut -d'T' -f1) - # Format the date as "Feb 21" style (GNU date -d first, then BSD date -j -f, then fallback) - release_date_display=$(date -d "$release_date" "+%b %d" 2>/dev/null || date -j -f "%Y-%m-%d" "$release_date" "+%b %d" 2>/dev/null || echo "$release_date") + # Format the date as "Feb 5" style — non-zero-padded day (GNU %-d first, then BSD %e trimmed, then fallback) + release_date_display=$(date -d "$release_date" "+%b %-d" 2>/dev/null || date -j -f "%Y-%m-%d" "$release_date" "+%b %e" 2>/dev/null | sed 's/ / /' || echo "$release_date") # Use name if different from tag, otherwise just show tag if [ "$release_name" != "$release_tag" ] && [ -n "$release_name" ]; then release_label="${release_name} (${release_tag})"