Update README What's New #33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Update README What's New | |
| on: | |
| workflow_run: | |
| workflows: ["Daily Change Summary"] | |
| types: [completed] | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| update-readme: | |
| if: >- | |
| github.event_name == 'workflow_dispatch' || | |
| github.event.workflow_run.conclusion == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Check for commits | |
| id: check | |
| run: | | |
| git checkout main | |
| git log --oneline -1 | |
| SINCE=$(date -u -d "24 hours ago" +%Y-%m-%dT%H:%M:%S) | |
| echo "SINCE=$SINCE" | |
| COUNT=$(git log --since="$SINCE" --oneline --no-merges | wc -l) | |
| echo "commit_count=$COUNT" >> "$GITHUB_OUTPUT" | |
| echo "since=$SINCE" >> "$GITHUB_OUTPUT" | |
| if [ "$COUNT" -eq "0" ]; then | |
| echo "No commits in the last 24 hours, skipping." | |
| else | |
| echo "Found $COUNT commits since $SINCE" | |
| fi | |
| - name: Setup Node.js | |
| if: steps.check.outputs.commit_count != '0' | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '25' | |
| - name: Install Copilot CLI | |
| if: steps.check.outputs.commit_count != '0' | |
| run: npm install -g @github/copilot | |
| - name: Detect significant features | |
| if: steps.check.outputs.commit_count != '0' | |
| id: detect | |
| env: | |
| COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT }} | |
| run: | | |
| SINCE="${{ steps.check.outputs.since }}" | |
| GIT_LOG=$(git log main --since="$SINCE" --pretty=format:"%h %s (%an)" --no-merges) | |
| OLDEST=$(git log main --since="$SINCE" --format="%H" --no-merges | tail -1) | |
| DIFF_STAT=$(git diff --stat "${OLDEST}^..main" 2>/dev/null || echo "N/A") | |
| DIFF_NAMES=$(git diff --name-only "${OLDEST}^..main" 2>/dev/null || echo "N/A") | |
| PROMPT=$(cat <<'PROMPT_EOF' | |
| You are a release-notes analyst for a Next.js + TypeScript AI research assistant project. | |
| Analyze the following git commits and file changes. Determine if there are any SIGNIFICANT NEW FEATURES worth highlighting in a "What's New" section of the README. | |
| CLASSIFICATION RULES: | |
| - SIGNIFICANT = new user-facing capability, new major integration, new UI panel/page, new API endpoint category, new workflow/system | |
| - NOT SIGNIFICANT = bug fixes, refactoring, dependency updates, CI/workflow changes, documentation-only changes, minor UI tweaks, code style changes, test additions | |
| OUTPUT FORMAT: | |
| If there ARE significant features, output EXACTLY in this format (no extra text before or after): | |
| ---BEGIN_FEATURES_EN--- | |
| - **Feature Name**: One-line English description | |
| ---END_FEATURES_EN--- | |
| ---BEGIN_FEATURES_ZH--- | |
| - **功能名称**: 一行中文描述 | |
| ---END_FEATURES_ZH--- | |
| If there are NO significant features, output EXACTLY: | |
| ---NO_FEATURES--- | |
| IMPORTANT: | |
| - Output TWO separate blocks: one English, one Chinese | |
| - Feature names and descriptions must correspond 1:1 between the two blocks | |
| - Keep descriptions concise (under 120 chars per line) | |
| - Group related commits into a single feature bullet | |
| - Maximum 3 feature bullets per day | |
| PROMPT_EOF | |
| ) | |
| RESULT=$(copilot -p "${PROMPT} | |
| ## Git Commits: | |
| ${GIT_LOG} | |
| ## Changed Files: | |
| ${DIFF_NAMES} | |
| ## File Change Stats: | |
| ${DIFF_STAT}") | |
| echo "$RESULT" | |
| echo "$RESULT" > /tmp/feature_detection.txt | |
| if echo "$RESULT" | grep -q "BEGIN_FEATURES_EN"; then | |
| echo "has_features=true" >> "$GITHUB_OUTPUT" | |
| # Extract English features | |
| sed -n '/---BEGIN_FEATURES_EN---/,/---END_FEATURES_EN---/p' \ | |
| /tmp/feature_detection.txt \ | |
| | grep -vF -- '---' > /tmp/new_features_en.txt | |
| # Extract Chinese features | |
| sed -n '/---BEGIN_FEATURES_ZH---/,/---END_FEATURES_ZH---/p' \ | |
| /tmp/feature_detection.txt \ | |
| | grep -vF -- '---' > /tmp/new_features_zh.txt | |
| echo "Detected English features:" | |
| cat /tmp/new_features_en.txt | |
| echo "Detected Chinese features:" | |
| cat /tmp/new_features_zh.txt | |
| else | |
| echo "has_features=false" >> "$GITHUB_OUTPUT" | |
| echo "No significant features detected." | |
| fi | |
| - name: Translate features for JA/FR/DE | |
| if: steps.detect.outputs.has_features == 'true' | |
| env: | |
| COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT }} | |
| run: | | |
| NEW_FEATURES_EN=$(cat /tmp/new_features_en.txt) | |
| NEW_FEATURES_ZH=$(cat /tmp/new_features_zh.txt) | |
| PROMPT=$(cat <<'PROMPT_EOF' | |
| You translate concise README "What's New" bullets for a software project. | |
| Convert the provided feature bullets into THREE aligned output blocks in this exact format: | |
| ---BEGIN_FEATURES_JA--- | |
| - **Feature Name**: Japanese description | |
| ---END_FEATURES_JA--- | |
| ---BEGIN_FEATURES_FR--- | |
| - **Nom de fonctionnalite**: Description francaise en ASCII uniquement | |
| ---END_FEATURES_FR--- | |
| ---BEGIN_FEATURES_DE--- | |
| - **Funktionsname**: Deutsche Beschreibung in ASCII nur | |
| ---END_FEATURES_DE--- | |
| RULES: | |
| - Keep bullet count and order exactly aligned with the English source. | |
| - Preserve the markdown bullet and bold feature-name structure. | |
| - Keep each line concise and skimmable. | |
| - Japanese may use natural Japanese. | |
| - French must use ASCII only, with no accented characters. | |
| - German must use ASCII only; use ae, oe, ue, and ss when needed. | |
| - Do not add commentary before or after the blocks. | |
| PROMPT_EOF | |
| ) | |
| RESULT=$(copilot -p "${PROMPT} | |
| ## English features: | |
| ${NEW_FEATURES_EN} | |
| ## Chinese features: | |
| ${NEW_FEATURES_ZH}") | |
| echo "$RESULT" | |
| echo "$RESULT" > /tmp/feature_translations.txt | |
| if echo "$RESULT" | grep -q "BEGIN_FEATURES_JA"; then | |
| sed -n '/---BEGIN_FEATURES_JA---/,/---END_FEATURES_JA---/p' \ | |
| /tmp/feature_translations.txt \ | |
| | grep -vF -- '---' > /tmp/new_features_ja.txt | |
| sed -n '/---BEGIN_FEATURES_FR---/,/---END_FEATURES_FR---/p' \ | |
| /tmp/feature_translations.txt \ | |
| | grep -vF -- '---' > /tmp/new_features_fr.txt | |
| sed -n '/---BEGIN_FEATURES_DE---/,/---END_FEATURES_DE---/p' \ | |
| /tmp/feature_translations.txt \ | |
| | grep -vF -- '---' > /tmp/new_features_de.txt | |
| echo "Detected Japanese features:" | |
| cat /tmp/new_features_ja.txt | |
| echo "Detected French features:" | |
| cat /tmp/new_features_fr.txt | |
| echo "Detected German features:" | |
| cat /tmp/new_features_de.txt | |
| else | |
| echo "Translation blocks not detected. Falling back to English bullets for JA/FR/DE." | |
| cp /tmp/new_features_en.txt /tmp/new_features_ja.txt | |
| cp /tmp/new_features_en.txt /tmp/new_features_fr.txt | |
| cp /tmp/new_features_en.txt /tmp/new_features_de.txt | |
| fi | |
| - name: Update READMEs | |
| if: steps.detect.outputs.has_features == 'true' | |
| run: | | |
| TODAY=$(date +%Y-%m-%d) | |
| MARKER_START="<!-- whats-new-start -->" | |
| # Update README.md (English homepage) | |
| NEW_FEATURES_EN=$(cat /tmp/new_features_en.txt) | |
| ENTRY_EN=$(printf '#### %s\n%s\n' "$TODAY" "$NEW_FEATURES_EN") | |
| awk -v marker="$MARKER_START" -v entry="$ENTRY_EN" ' | |
| { print } | |
| index($0, marker) { printf "\n%s\n", entry } | |
| ' README.md > /tmp/README_updated.md | |
| mv /tmp/README_updated.md README.md | |
| echo "README.md (EN) updated with new entry for $TODAY" | |
| # Update docs/README_CN.md (Chinese translation) | |
| NEW_FEATURES_ZH=$(cat /tmp/new_features_zh.txt) | |
| ENTRY_ZH=$(printf '#### %s\n%s\n' "$TODAY" "$NEW_FEATURES_ZH") | |
| awk -v marker="$MARKER_START" -v entry="$ENTRY_ZH" ' | |
| { print } | |
| index($0, marker) { printf "\n%s\n", entry } | |
| ' docs/README_CN.md > /tmp/README_CN_updated.md | |
| mv /tmp/README_CN_updated.md docs/README_CN.md | |
| echo "docs/README_CN.md (ZH) updated with new entry for $TODAY" | |
| # Update docs/README_JA.md (Japanese translation) | |
| NEW_FEATURES_JA=$(cat /tmp/new_features_ja.txt) | |
| ENTRY_JA=$(printf '#### %s\n%s\n' "$TODAY" "$NEW_FEATURES_JA") | |
| awk -v marker="$MARKER_START" -v entry="$ENTRY_JA" ' | |
| { print } | |
| index($0, marker) { printf "\n%s\n", entry } | |
| ' docs/README_JA.md > /tmp/README_JA_updated.md | |
| mv /tmp/README_JA_updated.md docs/README_JA.md | |
| echo "docs/README_JA.md (JA) updated with new entry for $TODAY" | |
| # Update docs/README_FR.md (French translation) | |
| NEW_FEATURES_FR=$(cat /tmp/new_features_fr.txt) | |
| ENTRY_FR=$(printf '#### %s\n%s\n' "$TODAY" "$NEW_FEATURES_FR") | |
| awk -v marker="$MARKER_START" -v entry="$ENTRY_FR" ' | |
| { print } | |
| index($0, marker) { printf "\n%s\n", entry } | |
| ' docs/README_FR.md > /tmp/README_FR_updated.md | |
| mv /tmp/README_FR_updated.md docs/README_FR.md | |
| echo "docs/README_FR.md (FR) updated with new entry for $TODAY" | |
| # Update docs/README_DE.md (German translation) | |
| NEW_FEATURES_DE=$(cat /tmp/new_features_de.txt) | |
| ENTRY_DE=$(printf '#### %s\n%s\n' "$TODAY" "$NEW_FEATURES_DE") | |
| awk -v marker="$MARKER_START" -v entry="$ENTRY_DE" ' | |
| { print } | |
| index($0, marker) { printf "\n%s\n", entry } | |
| ' docs/README_DE.md > /tmp/README_DE_updated.md | |
| mv /tmp/README_DE_updated.md docs/README_DE.md | |
| echo "docs/README_DE.md (DE) updated with new entry for $TODAY" | |
| - name: Trim old entries and collapse | |
| if: steps.detect.outputs.has_features == 'true' | |
| run: | | |
| python3 <<'PYEOF' | |
| import re | |
| MAX_ENTRIES = 10 | |
| VISIBLE_DATES = 2 # Number of most-recent unique dates shown expanded | |
| LANG_LABELS = { | |
| "README.md": "Show earlier updates", | |
| "docs/README_CN.md": "显示更早的更新", | |
| "docs/README_JA.md": "以前の更新を表示", | |
| "docs/README_FR.md": "Afficher les mises a jour precedentes", | |
| "docs/README_DE.md": "Aeltere Updates anzeigen", | |
| } | |
| for readme_file in [ | |
| "README.md", | |
| "docs/README_CN.md", | |
| "docs/README_JA.md", | |
| "docs/README_FR.md", | |
| "docs/README_DE.md", | |
| ]: | |
| with open(readme_file, "r", encoding="utf-8") as f: | |
| content = f.read() | |
| start_marker = "<!-- whats-new-start -->" | |
| end_marker = "<!-- whats-new-end -->" | |
| start_idx = content.find(start_marker) | |
| end_idx = content.find(end_marker) | |
| if start_idx == -1 or end_idx == -1: | |
| print(f"{readme_file}: Markers not found, skipping trim.") | |
| continue | |
| before = content[:start_idx + len(start_marker)] | |
| section = content[start_idx + len(start_marker):end_idx] | |
| after = content[end_idx:] | |
| # Strip any existing <details> wrapper from previous runs | |
| section = re.sub( | |
| r'<details>\s*<summary>.*?</summary>\s*(.*?)\s*</details>', | |
| r'\1', | |
| section, | |
| flags=re.DOTALL, | |
| ) | |
| # Split into date-headed entries | |
| entries = re.split(r'(?=\n#### \d{4}-\d{2}-\d{2})', section) | |
| entries = [e for e in entries if e.strip() and '####' in e] | |
| if len(entries) > MAX_ENTRIES: | |
| entries = entries[:MAX_ENTRIES] | |
| print(f"{readme_file}: Trimmed to {MAX_ENTRIES} entries.") | |
| else: | |
| print(f"{readme_file}: Have {len(entries)} entries (max {MAX_ENTRIES}), no trimming needed.") | |
| # Determine split point: first VISIBLE_DATES unique dates stay expanded | |
| seen_dates = [] | |
| split_idx = len(entries) | |
| for i, entry in enumerate(entries): | |
| m = re.search(r'#### (\d{4}-\d{2}-\d{2})', entry) | |
| if m and m.group(1) not in seen_dates: | |
| seen_dates.append(m.group(1)) | |
| if len(seen_dates) > VISIBLE_DATES: | |
| split_idx = i | |
| break | |
| visible = entries[:split_idx] | |
| collapsed = entries[split_idx:] | |
| result = "\n".join(visible) | |
| if collapsed: | |
| label = LANG_LABELS.get(readme_file, "Show earlier updates") | |
| result += "\n\n<details>\n<summary>" + label + "</summary>\n" | |
| result += "\n".join(collapsed) | |
| result += "\n\n</details>\n" | |
| result += "\n\n" | |
| new_content = before + "\n" + result + after | |
| with open(readme_file, "w", encoding="utf-8") as f: | |
| f.write(new_content) | |
| print(f"{readme_file}: {len(visible)} entries visible, {len(collapsed)} collapsed.") | |
| PYEOF | |
| - name: Create branch, PR and merge | |
| if: steps.detect.outputs.has_features == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TODAY=$(date +%Y-%m-%d) | |
| BRANCH="auto/whats-new-${TODAY}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # Create or reset branch from current state (READMEs already modified) | |
| git checkout -B "${BRANCH}" | |
| git add README.md docs/README_CN.md docs/README_JA.md docs/README_FR.md docs/README_DE.md | |
| git commit -m "docs: update What's New section (${TODAY})" || { | |
| echo "No changes to commit." | |
| exit 0 | |
| } | |
| git push origin "${BRANCH}" --force-with-lease | |
| # Check if PR already exists | |
| EXISTING_PR=$(gh pr list --head "${BRANCH}" --json number --jq '.[0].number' 2>/dev/null || echo "") | |
| if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then | |
| echo "PR #${EXISTING_PR} already exists. Branch updated." | |
| PR_NUMBER="$EXISTING_PR" | |
| else | |
| # Build PR body in a file to avoid heredoc quoting issues with backticks | |
| NEW_FEATURES_EN=$(cat /tmp/new_features_en.txt) | |
| NEW_FEATURES_ZH=$(cat /tmp/new_features_zh.txt) | |
| cat > /tmp/pr_body.md <<'BODY_EOF' | |
| ## Summary | |
| Auto-detected significant new features from today's commits and updated the **What's New** section in `README.md` (English homepage), `docs/README_CN.md` (Chinese translation), and lightweight localized summaries in `docs/README_JA.md`, `docs/README_FR.md`, and `docs/README_DE.md`. | |
| BODY_EOF | |
| printf '\n### New entries (English):\n\n%s\n' "$NEW_FEATURES_EN" >> /tmp/pr_body.md | |
| printf '\n### New entries (Chinese):\n\n%s\n' "$NEW_FEATURES_ZH" >> /tmp/pr_body.md | |
| printf '\n---\n*Auto-generated by the `readme-whats-new` workflow.*\n' >> /tmp/pr_body.md | |
| PR_URL=$(gh pr create \ | |
| --title "docs: What's New - ${TODAY}" \ | |
| --body-file /tmp/pr_body.md \ | |
| --label "documentation" \ | |
| --base main \ | |
| --head "${BRANCH}") | |
| echo "Created PR: ${PR_URL}" | |
| PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') | |
| fi | |
| # Auto-merge the PR | |
| if [ -n "$PR_NUMBER" ]; then | |
| echo "Merging PR #${PR_NUMBER} ..." | |
| gh pr merge "$PR_NUMBER" --squash --delete-branch | |
| echo "PR #${PR_NUMBER} merged successfully." | |
| fi |