From cfc80a8eb8a64d0815c01f5b6f94f3618d432490 Mon Sep 17 00:00:00 2001 From: Automaker Date: Sat, 23 May 2026 17:40:58 -0700 Subject: [PATCH] chore(release): use protoLabsAI/release-tools@v1 for release notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the local fork of \`scripts/rewrite-release-notes.mjs\` (210 LOC) with the upstream composite Action at \`protoLabsAI/release-tools@v1\`. The local script was a stale copy of the same logic already centralized in the release-tools repo — exactly the consolidation pattern we want. Side benefit: switches release-notes generation from a direct \`ANTHROPIC_API_KEY\` call to the protoLabs LLM gateway (default model \`protolabs/fast\`), matching the rest of the agent stack. Removes one more place we hand out raw Anthropic keys. Notes: - The composite Action's secret naming is \`DISCORD_RELEASE_WEBHOOK\` but our existing secret is \`DISCORD_DEV_WEBHOOK\`. We pass our secret via the env-var mapping so we don't need a new repo secret. - Already-consumed elsewhere: \`code-review.yml\` uses \`npx -p github:protoLabsAI/release-tools review-code\`. This PR brings \`auto-release.yml\` into line. Future consolidation opportunities for release-tools (not blocking this PR): - The "Pre-flight: format/lint/typecheck" pattern in \`checks.yml\` could become a reusable workflow. - The "setup-node + install + build packages" pattern in \`pr-check.yml\` could become a composite action. - \`scripts/post-review-findings.mjs\` (86 LOC) duplicates a "post sticky PR comment" pattern that could live in release-tools alongside \`review-code\`. Docs updated to reflect the new entry points (\`docs/self-hosting/ci-cd.md\`, \`docs/internal/dev/release.md\`, \`docs/internal/dev/versioning.md\`). Co-Authored-By: Claude Opus 4.7 --- .github/workflows/auto-release.yml | 17 ++- docs/internal/dev/release.md | 20 +-- docs/internal/dev/versioning.md | 4 +- docs/self-hosting/ci-cd.md | 10 +- scripts/rewrite-release-notes.mjs | 210 ----------------------------- 5 files changed, 30 insertions(+), 231 deletions(-) delete mode 100755 scripts/rewrite-release-notes.mjs diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index fe0465929..383e9e8af 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -92,15 +92,24 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Rewrite and post release notes to Discord + - name: Resolve previous tag + id: prev if: steps.version.outputs.already_tagged != 'true' && env.DISCORD_DEV_WEBHOOK != '' - continue-on-error: true run: | VERSION="v${{ steps.version.outputs.version }}" PREV_TAG=$(git tag --sort=-v:refname | grep -v "^${VERSION}$" | head -1) - node scripts/rewrite-release-notes.mjs "$VERSION" "$PREV_TAG" --post-discord + echo "tag=$PREV_TAG" >> $GITHUB_OUTPUT + + - name: Rewrite and post release notes to Discord + if: steps.version.outputs.already_tagged != 'true' && env.DISCORD_DEV_WEBHOOK != '' + continue-on-error: true + uses: protoLabsAI/release-tools@v1 + with: + version: v${{ steps.version.outputs.version }} + previous-version: ${{ steps.prev.outputs.tag }} env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + GATEWAY_API_KEY: ${{ secrets.GATEWAY_API_KEY }} + DISCORD_RELEASE_WEBHOOK: ${{ secrets.DISCORD_DEV_WEBHOOK }} - name: Alert on release failure if: failure() && steps.version.outputs.already_tagged != 'true' diff --git a/docs/internal/dev/release.md b/docs/internal/dev/release.md index db03534f8..07939ce5d 100644 --- a/docs/internal/dev/release.md +++ b/docs/internal/dev/release.md @@ -2,10 +2,10 @@ ## Overview -protoLabs Studio uses an LLM-powered release notes rewriter to transform raw conventional commit messages into polished, user-facing release notes. The system has two components: +protoLabs Studio uses an LLM-powered release notes rewriter to transform raw conventional commit messages into polished, user-facing release notes. Centralized in [`@protolabsai/release-tools`](https://github.com/protoLabsAI/release-tools): -1. **Prompt template** (`libs/prompts/src/release-notes.ts`) — reusable from any TypeScript context -2. **CLI script** (`scripts/rewrite-release-notes.mjs`) — standalone runner for CI or manual use +1. **Composite GitHub Action** (`protoLabsAI/release-tools@v1`) — used by `.github/workflows/auto-release.yml` +2. **npm CLI** (`npx @protolabsai/release-tools rewrite-release-notes`) — standalone runner for manual use ## How It Works @@ -42,16 +42,16 @@ Raw commits like `feat(ui): wire file editor to upstream parity` become grouped, ```bash # Auto-detect latest two tags -node scripts/rewrite-release-notes.mjs +npx @protolabsai/release-tools rewrite-release-notes # Specify versions explicitly -node scripts/rewrite-release-notes.mjs v0.30.1 v0.29.0 +npx @protolabsai/release-tools rewrite-release-notes v0.30.1 v0.29.0 # Preview the prompt without calling Claude -node scripts/rewrite-release-notes.mjs --dry-run +npx @protolabsai/release-tools rewrite-release-notes --dry-run # Generate and post to Discord #dev -node scripts/rewrite-release-notes.mjs --post-discord +npx @protolabsai/release-tools rewrite-release-notes --post-discord ``` ### Flags @@ -124,7 +124,7 @@ The `auto-release.yml` workflow calls the rewriter script as the final step afte run: | VERSION="v${{ steps.version.outputs.version }}" PREV_TAG=$(git tag --sort=-v:refname | grep -v "^${VERSION}$" | head -1) - node scripts/rewrite-release-notes.mjs "$VERSION" "$PREV_TAG" --post-discord + npx @protolabsai/release-tools rewrite-release-notes "$VERSION" "$PREV_TAG" --post-discord env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ``` @@ -133,9 +133,9 @@ The `auto-release.yml` workflow calls the rewriter script as the final step afte - **Enabled by default**: Wired into `auto-release.yml` — runs on every push to `main` - **Requires**: `ANTHROPIC_API_KEY` (Claude API) -- **Manual runs**: `node scripts/rewrite-release-notes.mjs` locally with `ANTHROPIC_API_KEY` set +- **Manual runs**: `npx @protolabsai/release-tools rewrite-release-notes` locally with `ANTHROPIC_API_KEY` set - **Disable in CI**: Remove or comment out the "Rewrite and post release notes" step in `auto-release.yml`; the GitHub Release body still contains the raw auto-generated notes from `gh release create` ## Model Selection -The CLI script uses `claude-haiku-4-5-20251001` (Haiku 4.5) for speed and cost efficiency. Release notes rewriting is a structured text task that does not require Sonnet or Opus capabilities. To change the model, edit the `model` field in the `callClaude()` function in `scripts/rewrite-release-notes.mjs`. +Defaults to `protolabs/fast` via the protoLabs LLM gateway. Release notes rewriting is a structured text task that does not require a heavier model. To override, set the `model` input on the Action (e.g. `model: protolabs/smart`) or pass `--model ` to the CLI. See [`@protolabsai/release-tools`](https://github.com/protoLabsAI/release-tools) for the full input/env reference. diff --git a/docs/internal/dev/versioning.md b/docs/internal/dev/versioning.md index 6495ce106..294912260 100644 --- a/docs/internal/dev/versioning.md +++ b/docs/internal/dev/versioning.md @@ -110,10 +110,10 @@ Raw GitHub-generated release notes (which list PR titles) can be rewritten into ```bash # Auto-detect tags and generate notes -node scripts/rewrite-release-notes.mjs +npx @protolabsai/release-tools rewrite-release-notes # Specify versions + post to Discord -node scripts/rewrite-release-notes.mjs v0.30.1 v0.29.0 --post-discord +npx @protolabsai/release-tools rewrite-release-notes v0.30.1 v0.29.0 --post-discord ``` The rewriter filters out merge/chore/promote commits, sends the rest to Claude (Haiku 4.5), and returns themed sections grouped by user impact. The prompt template is also available programmatically via `@protolabsai/prompts`: diff --git a/docs/self-hosting/ci-cd.md b/docs/self-hosting/ci-cd.md index 9e1c51c26..9ded51a1a 100644 --- a/docs/self-hosting/ci-cd.md +++ b/docs/self-hosting/ci-cd.md @@ -202,7 +202,7 @@ auto-release.yml ## Release Notes Rewriting -An LLM-powered release notes rewriter transforms raw conventional commits into polished, user-facing release notes. Available as both a reusable prompt template (`libs/prompts/src/release-notes.ts`) and a standalone CLI script (`scripts/rewrite-release-notes.mjs`). +An LLM-powered release notes rewriter transforms raw conventional commits into polished, user-facing release notes. Centralized in [`@protolabsai/release-tools`](https://github.com/protoLabsAI/release-tools) — exposed as both a composite GitHub Action (`protoLabsAI/release-tools@v1`) and an npm CLI (`npx @protolabsai/release-tools rewrite-release-notes`). ### How It Works @@ -216,16 +216,16 @@ An LLM-powered release notes rewriter transforms raw conventional commits into p ```bash # Auto-detect latest two tags -node scripts/rewrite-release-notes.mjs +npx @protolabsai/release-tools rewrite-release-notes # Specify versions -node scripts/rewrite-release-notes.mjs v0.30.1 v0.29.0 +npx @protolabsai/release-tools rewrite-release-notes v0.30.1 v0.29.0 # Preview prompt without calling API -node scripts/rewrite-release-notes.mjs --dry-run +npx @protolabsai/release-tools rewrite-release-notes --dry-run # Generate and post to Discord -node scripts/rewrite-release-notes.mjs --post-discord +npx @protolabsai/release-tools rewrite-release-notes --post-discord ``` ### CI Integration diff --git a/scripts/rewrite-release-notes.mjs b/scripts/rewrite-release-notes.mjs deleted file mode 100755 index f676f121f..000000000 --- a/scripts/rewrite-release-notes.mjs +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env node -/** - * Rewrite raw release commits into polished user-facing release notes via Claude API. - * - * Requires: ANTHROPIC_API_KEY environment variable - * - * Usage: - * node scripts/rewrite-release-notes.mjs [version] [previous-version] - * - * Examples: - * node scripts/rewrite-release-notes.mjs v0.30.1 v0.29.0 - * node scripts/rewrite-release-notes.mjs # auto-detects latest + previous tag - * - * Flags: - * --post-discord Post the result to #dev via DISCORD_DEV_WEBHOOK - * --dry-run Print the prompt without calling Claude (debug) - */ - -import { execSync } from 'node:child_process'; - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -function run(cmd) { - return execSync(cmd, { encoding: 'utf-8' }).trim(); -} - -function getTags() { - const tags = run('git tag --sort=-v:refname').split('\n').filter(Boolean); - if (tags.length < 2) { - console.error('Need at least 2 tags to compare. Found:', tags.length); - process.exit(1); - } - return { latest: tags[0], previous: tags[1] }; -} - -function getCommitsBetween(fromTag, toTag) { - const log = run(`git log ${fromTag}..${toTag} --pretty=format:"%s"`); - if (!log) return []; - return log - .split('\n') - .map((line) => line.replace(/^"|"$/g, '')) - .filter(Boolean); -} - -// --------------------------------------------------------------------------- -// Prompt (mirrors libs/prompts/src/release-notes.ts) -// --------------------------------------------------------------------------- - -const SYSTEM_PROMPT = `You are a release notes writer for protoLabs Studio, an autonomous AI development platform. - -Voice: Technical, direct, pragmatic. Speak to builders. No marketing fluff, no AI hype words ("revolutionizing", "game-changing"), no filler. - -Rules: -- Write a short intro sentence (what this release is about in one line) -- Group changes into 2-4 themed sections with bold headers (not raw commit categories — group by what the user cares about) -- Each item: one sentence, present tense, explains the user-facing impact -- Skip internal-only changes (CI config, version bumps, merge commits, chore commits) unless they fix a user-visible problem -- Skip "promote" / "Merge" / "chore: release" commits entirely -- If a commit message is unclear, infer the impact from context or omit it -- End with a one-liner on what's next if the commit history suggests ongoing work -- Keep the total output under 300 words -- Use plain markdown: **bold** for section headers, - for bullets -- No emojis -- Do NOT wrap output in code fences — output the markdown directly`; - -function buildPrompt(version, previousVersion, commits) { - const filtered = commits.filter((c) => { - const lower = c.toLowerCase(); - return ( - !lower.startsWith('merge ') && - !lower.startsWith('chore: release') && - !lower.startsWith('promote') - ); - }); - - const commitList = filtered.map((c) => `- ${c}`).join('\n'); - - return `Rewrite these raw commit messages into user-facing release notes for ${version} (previous: ${previousVersion}). - -Raw commits: -${commitList || '(no meaningful commits — write a brief maintenance release note)'}`; -} - -// --------------------------------------------------------------------------- -// Claude API call -// --------------------------------------------------------------------------- - -async function callClaude(systemPrompt, userPrompt) { - const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) { - console.error('ANTHROPIC_API_KEY not set.'); - process.exit(1); - } - - const res = await fetch('https://api.anthropic.com/v1/messages', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': apiKey, - 'anthropic-version': '2023-06-01', - }, - body: JSON.stringify({ - model: 'claude-haiku-4-5-20251001', - max_tokens: 1024, - system: systemPrompt, - messages: [{ role: 'user', content: userPrompt }], - }), - }); - - if (!res.ok) { - const body = await res.text(); - console.error(`Claude API error: ${res.status} ${body}`); - process.exit(1); - } - - const data = await res.json(); - return data.content[0].text; -} - -// --------------------------------------------------------------------------- -// Discord posting -// --------------------------------------------------------------------------- - -async function postToDiscord(version, notes) { - const webhook = process.env.DISCORD_DEV_WEBHOOK; - if (!webhook) { - console.error('DISCORD_DEV_WEBHOOK not set. Skipping Discord post.'); - return false; - } - - const releaseUrl = `https://github.com/protoLabsAI/protoMaker/releases/tag/${version}`; - - // Truncate to Discord embed limit - const truncated = notes.length > 3900 ? notes.slice(0, 3900) + '\n...' : notes; - - const payload = { - embeds: [ - { - title: `${version} Alpha`, - url: releaseUrl, - description: truncated, - color: 5763719, - }, - ], - }; - - const res = await fetch(webhook, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }); - - if (!res.ok) { - console.error(`Discord webhook failed: ${res.status} ${res.statusText}`); - return false; - } - - console.log('Posted to Discord'); - return true; -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -const args = process.argv.slice(2); -const flags = args.filter((a) => a.startsWith('--')); -const positional = args.filter((a) => !a.startsWith('--')); - -const dryRun = flags.includes('--dry-run'); -const postDiscord = flags.includes('--post-discord'); - -// Resolve versions -let version, previousVersion; -if (positional.length >= 2) { - version = positional[0]; - previousVersion = positional[1]; -} else { - const tags = getTags(); - version = positional[0] || tags.latest; - previousVersion = positional[1] || tags.previous; -} - -console.log(`Generating release notes: ${previousVersion} -> ${version}`); - -const commits = getCommitsBetween(previousVersion, version); -console.log(`Found ${commits.length} commits\n`); - -const userPrompt = buildPrompt(version, previousVersion, commits); - -if (dryRun) { - console.log('=== SYSTEM PROMPT ==='); - console.log(SYSTEM_PROMPT); - console.log('\n=== USER PROMPT ==='); - console.log(userPrompt); - process.exit(0); -} - -console.log('Calling Claude API (haiku)...\n'); -const notes = await callClaude(SYSTEM_PROMPT, userPrompt); - -console.log('=== RELEASE NOTES ==='); -console.log(notes); -console.log('=====================\n'); - -if (postDiscord) { - await postToDiscord(version, notes); -}