From a1741f4322720701c8d6e57fa61ba62d466a30ee Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Fri, 17 Apr 2026 20:21:51 -0700 Subject: [PATCH] docs: update CHANGELOG.md for v12.2.0 + make generator incremental Script now reads existing CHANGELOG.md, skips releases already documented, only fetches bodies for new releases, and prepends them. Pass --full to force complete regeneration. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 31 ++++++++ scripts/generate-changelog.js | 143 +++++++++++++++++++++------------- 2 files changed, 120 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06689b14..32ece095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [12.2.0] - 2026-04-18 + +## Highlights + +**Worktree Adoption** — When a git worktree is merged back into its parent branch, its observations are now consolidated into the parent project's view, so memory follows the code after a merge. + +## Features + +- **Worktree adoption engine** — consolidates merged-worktree observations under the parent project (#2052) +- **`npx claude-mem adopt`** — new CLI command with `--dry-run` and `--branch X` flags for manual adoption +- **Auto-adoption on worker startup** — merged worktrees are adopted automatically when the worker service starts +- **CWD-based project remap** — project identity derived from `pending_messages.cwd`, applied on worker startup +- **Parent + worktree read scope** — worktree sessions now include parent repo observations in their read scope +- **Composite project names** — parent/worktree naming prevents observations from crossing worktrees +- **Merged-into-parent badge** — UI now flags observations that have been adopted from a merged worktree +- **Observer-sessions project hidden** — internal bookkeeping project no longer appears in UI lists + +## Fixes + +- Drop orphan flag when filtering empty-string spawn args (#2049) +- Self-heal Chroma metadata on re-run +- Schema guard, startup adoption path, and query parity hardening +- Git operation timeouts + dry-run sentinel fixes +- Context derivation uses explicit `projects` array rather than cwd + +## Chores + +- Removed auto-generated per-directory `CLAUDE.md` files across the tree + +**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v12.1.6...v12.2.0 + ## [12.1.6] - 2026-04-16 ## Fix diff --git a/scripts/generate-changelog.js b/scripts/generate-changelog.js index 127e203b..31bedade 100644 --- a/scripts/generate-changelog.js +++ b/scripts/generate-changelog.js @@ -1,13 +1,26 @@ #!/usr/bin/env node /** - * Generate CHANGELOG.md from GitHub releases + * Generate CHANGELOG.md from GitHub releases. * - * Fetches all releases from GitHub and formats them into Keep a Changelog format. + * Incremental by default: reads existing CHANGELOG.md, only fetches releases + * newer than the newest version already documented, and prepends them. + * + * Pass --full to force a complete regeneration from every release. */ import { execSync } from 'child_process'; -import { writeFileSync } from 'fs'; +import { writeFileSync, readFileSync, existsSync } from 'fs'; + +const CHANGELOG_PATH = 'CHANGELOG.md'; +const HEADER_LINES = [ + '# Changelog', + '', + 'All notable changes to this project will be documented in this file.', + '', + 'The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).', + '', +]; function exec(command) { try { @@ -19,28 +32,20 @@ function exec(command) { } } -function getReleases() { - console.log('📋 Fetching releases from GitHub...'); +function listReleases() { const releasesJson = exec('gh release list --limit 1000 --json tagName,publishedAt,name'); - const releases = JSON.parse(releasesJson); + return JSON.parse(releasesJson); +} - // Fetch body for each release - console.log(`📥 Fetching details for ${releases.length} releases...`); - for (const release of releases) { - const body = exec(`gh release view ${release.tagName} --json body --jq '.body'`).trim(); - release.body = body; - } - - return releases; +function fetchReleaseBody(tagName) { + return exec(`gh release view ${tagName} --json body --jq '.body'`).trim(); } function formatDate(isoDate) { - const date = new Date(isoDate); - return date.toISOString().split('T')[0]; // YYYY-MM-DD + return new Date(isoDate).toISOString().split('T')[0]; } function cleanReleaseBody(body) { - // Remove the "Generated with Claude Code" footer return body .replace(/🤖 Generated with \[Claude Code\].*$/s, '') .replace(/---\n*$/s, '') @@ -48,62 +53,92 @@ function cleanReleaseBody(body) { } function extractVersion(tagName) { - // Remove 'v' prefix from tag name return tagName.replace(/^v/, ''); } -function generateChangelog(releases) { - console.log(`📝 Generating CHANGELOG.md from ${releases.length} releases...`); - - const lines = [ - '# Changelog', - '', - 'All notable changes to this project will be documented in this file.', - '', - 'The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).', - '', - ]; - - // Sort releases by date (newest first) - releases.sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt)); - - for (const release of releases) { - const version = extractVersion(release.tagName); - const date = formatDate(release.publishedAt); - const body = cleanReleaseBody(release.body); - - // Add version header - lines.push(`## [${version}] - ${date}`); +function renderEntry(release) { + const version = extractVersion(release.tagName); + const date = formatDate(release.publishedAt); + const body = cleanReleaseBody(release.body); + const lines = [`## [${version}] - ${date}`, '']; + if (body) { + const bodyWithoutHeader = body.replace(/^##?\s+v?[\d.]+.*?\n\n?/m, ''); + lines.push(bodyWithoutHeader); lines.push(''); - - // Add release body - if (body) { - // Remove the initial markdown heading if it exists (e.g., "## v5.5.0 (2025-11-11)") - const bodyWithoutHeader = body.replace(/^##?\s+v?[\d.]+.*?\n\n?/m, ''); - lines.push(bodyWithoutHeader); - lines.push(''); - } } - return lines.join('\n'); } +/** + * Parse the existing CHANGELOG.md and return: + * - knownVersions: Set of version strings already present + * - body: the content following the standard header (entries only) + */ +function readExistingChangelog() { + if (!existsSync(CHANGELOG_PATH)) { + return { knownVersions: new Set(), body: '' }; + } + const content = readFileSync(CHANGELOG_PATH, 'utf-8'); + const knownVersions = new Set(); + const versionHeaderRe = /^## \[([^\]]+)\]/gm; + let match; + while ((match = versionHeaderRe.exec(content)) !== null) { + knownVersions.add(match[1]); + } + // Strip the standard header so we can re-emit it cleanly + const firstEntryIndex = content.search(/^## \[/m); + const body = firstEntryIndex === -1 ? '' : content.slice(firstEntryIndex); + return { knownVersions, body }; +} + function main() { + const fullRegen = process.argv.includes('--full'); + console.log('🔧 Generating CHANGELOG.md from GitHub releases...\n'); - const releases = getReleases(); + const { knownVersions, body: existingBody } = fullRegen + ? { knownVersions: new Set(), body: '' } + : readExistingChangelog(); - if (releases.length === 0) { + console.log('📋 Fetching release list from GitHub...'); + const allReleases = listReleases(); + + if (allReleases.length === 0) { console.log('⚠️ No releases found'); return; } - const changelog = generateChangelog(releases); + const newReleases = allReleases.filter( + (release) => !knownVersions.has(extractVersion(release.tagName)), + ); - writeFileSync('CHANGELOG.md', changelog, 'utf-8'); + if (newReleases.length === 0) { + console.log('✅ CHANGELOG.md is already up to date.'); + return; + } + + console.log( + `📥 Fetching bodies for ${newReleases.length} new release(s)` + + (fullRegen ? '' : ` (${knownVersions.size} already in CHANGELOG)`) + + '...', + ); + for (const release of newReleases) { + release.body = fetchReleaseBody(release.tagName); + } + + newReleases.sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt)); + + const newEntriesBlock = newReleases.map(renderEntry).join('\n'); + + const finalBody = existingBody + ? `${newEntriesBlock}\n${existingBody}`.trimEnd() + '\n' + : `${newEntriesBlock}`.trimEnd() + '\n'; + + const changelog = HEADER_LINES.join('\n') + '\n' + finalBody; + writeFileSync(CHANGELOG_PATH, changelog, 'utf-8'); console.log('\n✅ CHANGELOG.md generated successfully!'); - console.log(` ${releases.length} releases processed`); + console.log(` ${newReleases.length} new release(s) prepended`); } main();