From b46b52045c064f4f8b4fc04e88d034af385b049b Mon Sep 17 00:00:00 2001 From: atharva-bhange Date: Thu, 23 Jul 2026 16:34:35 +0530 Subject: [PATCH 1/3] docs(changelog): add automation insertion marker Co-Authored-By: Claude Fable 5 --- src/pages/changelog.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/changelog.mdx b/src/pages/changelog.mdx index 47b2945d..bef7ab3f 100644 --- a/src/pages/changelog.mdx +++ b/src/pages/changelog.mdx @@ -8,6 +8,8 @@ import Callout from '../components/docs/Callout.astro'; Stay up to date with the latest features, improvements, and bug fixes. +{/* changelog:insert-below — automation inserts new releases here; do not remove */} + ## v2.0.0 - December 2024 From 2262199c230d2295356c0511cc1cd85ed7633c01 Mon Sep 17 00:00:00 2001 From: atharva-bhange Date: Thu, 23 Jul 2026 16:34:35 +0530 Subject: [PATCH 2/3] ci(changelog): add release transform script with tests Co-Authored-By: Claude Fable 5 --- scripts/changelog-from-release.mjs | 58 +++++++++++++++++++++++++ scripts/changelog-from-release.test.mjs | 49 +++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 scripts/changelog-from-release.mjs create mode 100644 scripts/changelog-from-release.test.mjs diff --git a/scripts/changelog-from-release.mjs b/scripts/changelog-from-release.mjs new file mode 100644 index 00000000..4d5a572f --- /dev/null +++ b/scripts/changelog-from-release.mjs @@ -0,0 +1,58 @@ +#!/usr/bin/env node +// Transforms a GitHub Release body (release-please format) into a changelog.mdx +// section and inserts it below the marker. Usage: +// node scripts/changelog-from-release.mjs +import { readFileSync, writeFileSync } from "node:fs"; + +const MARKER = "{/* changelog:insert-below — automation inserts new releases here; do not remove */}"; +const SECTION_MAP = new Map([ + ["Features", "New Features"], + ["Bug Fixes", "Bug Fixes"], + ["Performance Improvements", "Improvements"], + ["Reverts", "Reverts"], +]); + +export function transform(version, body, now = new Date()) { + const month = now.toLocaleString("en-US", { month: "long", year: "numeric" }); + const lines = body.split("\n"); + const out = [`## ${version} - ${month}`, ""]; + let currentMapped = null; + let breaking = []; + let inBreaking = false; + for (const line of lines) { + const h = line.match(/^#{2,3}\s+(.*)$/); + if (h) { + const title = h[1].trim(); + if (/BREAKING CHANGES/i.test(title)) { inBreaking = true; currentMapped = null; continue; } + inBreaking = false; + currentMapped = SECTION_MAP.get(title) ?? null; + if (currentMapped) out.push(`### ${currentMapped}`, ""); + continue; + } + if (inBreaking && line.trim().startsWith("*")) breaking.push(line.replace(/^\s*\*/, "-")); + else if (currentMapped && line.trim().startsWith("*")) out.push(line.replace(/^\s*\*/, "-")); + else if (currentMapped && line.trim() === "") { + if (out[out.length - 1] !== "") out.push(""); + } + } + if (breaking.length) out.push("### Breaking Changes", "", ...breaking, ""); + if (out[out.length - 1] !== "") out.push(""); + out.push("---", ""); + return out.join("\n"); +} + +export function insert(changelog, section) { + const idx = changelog.indexOf(MARKER); + if (idx === -1) throw new Error("changelog marker not found"); + const insertAt = idx + MARKER.length; + return changelog.slice(0, insertAt) + "\n\n" + section.trimEnd() + "\n" + changelog.slice(insertAt); +} + +const isMain = process.argv[1] && import.meta.url.endsWith(process.argv[1].split("/").pop()); +if (isMain && process.argv.length >= 5) { + const [, , version, bodyFile, changelogFile] = process.argv; + const body = readFileSync(bodyFile, "utf8"); + const changelog = readFileSync(changelogFile, "utf8"); + writeFileSync(changelogFile, insert(changelog, transform(version, body))); + console.log(`Inserted ${version} into ${changelogFile}`); +} diff --git a/scripts/changelog-from-release.test.mjs b/scripts/changelog-from-release.test.mjs new file mode 100644 index 00000000..96378261 --- /dev/null +++ b/scripts/changelog-from-release.test.mjs @@ -0,0 +1,49 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { transform, insert } from "./changelog-from-release.mjs"; + +const RELEASE_BODY = `## [2.1.0](https://github.com/future-agi/future-agi/compare/v2.0.0...v2.1.0) (2026-08-01) + +### ⚠ BREAKING CHANGES + +* **api:** remove deprecated /v1/eval endpoint + +### Features + +* **observe:** session-level trace grouping ([#901](https://github.com/future-agi/future-agi/pull/901)) +* **gateway:** streaming responses ([#905](https://github.com/future-agi/future-agi/pull/905)) + +### Bug Fixes + +* **tracer:** off-by-one in span pagination ([#903](https://github.com/future-agi/future-agi/pull/903)) + +### Chores + +* bump deps ([#900](https://github.com/future-agi/future-agi/pull/900)) +`; + +test("transform maps release-please sections to changelog sections", () => { + const s = transform("v2.1.0", RELEASE_BODY, new Date("2026-08-01T00:00:00Z")); + assert.match(s, /^## v2\.1\.0 - August 2026/m); + assert.match(s, /^### New Features/m); + assert.match(s, /session-level trace grouping/); + assert.match(s, /^### Bug Fixes/m); + assert.match(s, /^### Breaking Changes/m); + assert.match(s, /remove deprecated \/v1\/eval endpoint/); + assert.doesNotMatch(s, /Chores/); + assert.doesNotMatch(s, /bump deps/); + assert.match(s, /---\s*$/); +}); + +test("insert places section after marker and preserves the rest", () => { + const changelog = `intro\n\n{/* changelog:insert-below — automation inserts new releases here; do not remove */}\n\n## v2.0.0 - July 2026\nold entry\n`; + const out = insert(changelog, "## v2.1.0 - August 2026\nnew\n\n---"); + const iMarker = out.indexOf("changelog:insert-below"); + const iNew = out.indexOf("## v2.1.0"); + const iOld = out.indexOf("## v2.0.0"); + assert.ok(iMarker < iNew && iNew < iOld); +}); + +test("insert throws when marker missing", () => { + assert.throws(() => insert("no marker here", "x"), /marker not found/); +}); From 8edcb979c6edf417079ba4d872be2058b6fc25b8 Mon Sep 17 00:00:00 2001 From: atharva-bhange Date: Thu, 23 Jul 2026 16:34:36 +0530 Subject: [PATCH 3/3] ci(changelog): open changelog PR on platform-release dispatch Co-Authored-By: Claude Fable 5 --- .github/workflows/changelog-sync.yml | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/changelog-sync.yml diff --git a/.github/workflows/changelog-sync.yml b/.github/workflows/changelog-sync.yml new file mode 100644 index 00000000..1d9b76e5 --- /dev/null +++ b/.github/workflows/changelog-sync.yml @@ -0,0 +1,45 @@ +# Receives platform-release dispatch and opens an editorial PR adding the new +# version to the changelog page (RELEASE-PROCESS-PLAN.md §5.4). +name: changelog-sync + +on: + repository_dispatch: + types: [platform-release] + +permissions: + contents: write + pull-requests: write + +jobs: + open-changelog-pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Generate changelog entry + env: + VERSION: ${{ github.event.client_payload.version }} + BODY_B64: ${{ github.event.client_payload.release_body_b64 }} + run: | + set -eu + printf '%s' "$BODY_B64" | base64 -d > /tmp/release-body.md + node scripts/changelog-from-release.mjs "$VERSION" /tmp/release-body.md src/pages/changelog.mdx + - name: Open PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ github.event.client_payload.version }} + RELEASE_URL: ${{ github.event.client_payload.release_url }} + run: | + set -eu + branch="chore/changelog-${VERSION}" + git config user.name "futureagi-release-bot" + git config user.email "release-bot@futureagi.com" + git switch -c "$branch" + git add src/pages/changelog.mdx + git commit -m "docs(changelog): add ${VERSION}" + git push origin "$branch" + gh pr create --base main --head "$branch" \ + --title "docs(changelog): ${VERSION}" \ + --body "Auto-generated from the [${VERSION} release notes](${RELEASE_URL}). Edit for a product audience before merging — rewrite or drop raw commit bullets; merging as-is is acceptable."