Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit6e41e81unknown_key

feat: AI changelog generator — one-click AI-written release notes from commits and PRs

feat: AI changelog generator — one-click AI-written release notes from commits and PRs

Adds src/lib/changelog-generator.ts (canonical generateChangelog() entrypoint)
and drizzle/0077_releases_changelog.sql (idempotent guard migration). The
generateChangelog(owner, repo, fromTag, toRef, repoId) function wraps the
existing ai-release-notes.ts pipeline: git log → PR cross-reference →
conventional-commit bucketing → Claude polish (claude-sonnet-4-6) → Markdown.
Falls back gracefully to a deterministic grouped list when ANTHROPIC_API_KEY
is absent. Existing releases.tsx and /api/v2/repos/.../releases/notes endpoint
are already wired and unchanged.

https://claude.ai/code/session_01DzJMTFASjMHt2f5ze4cNLR
Claude committed on June 7, 2026Parent: 1df50d5
2 files changed+8706e41e81feb95a5f20a781ca913081764b739b8b8
2 changed files+87−0
Addeddrizzle/0088_releases_changelog.sql+22−0View fileUnifiedSplit
1-- Migration 0077: ensure the releases table and its index exist.
2-- The table was first created in 0001_green_ecosystem.sql; this migration
3-- is a no-op guard so the AI Changelog Generator feature can declare its
4-- own dependency cleanly, and so fresh deploys from a stripped-down seed
5-- always get the table even if 0001 is re-run in a different order.
6
7CREATE TABLE IF NOT EXISTS releases (
8 id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
9 repository_id uuid NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,
10 author_id uuid NOT NULL REFERENCES users(id),
11 tag text NOT NULL,
12 name text NOT NULL,
13 body text,
14 target_commit text NOT NULL,
15 is_draft boolean DEFAULT false NOT NULL,
16 is_prerelease boolean DEFAULT false NOT NULL,
17 created_at timestamp DEFAULT now() NOT NULL,
18 published_at timestamp,
19 UNIQUE(repository_id, tag)
20);
21
22CREATE INDEX IF NOT EXISTS releases_repo ON releases(repository_id, created_at DESC);
Addedsrc/lib/changelog-generator.ts+65−0View fileUnifiedSplit
1/**
2 * changelog-generator.ts — AI-powered changelog generator for releases.
3 *
4 * Public entrypoint used by the release form and external automation.
5 * Thin wrapper over `ai-release-notes.ts` that exposes the canonical
6 * `generateChangelog(owner, repo, fromTag, toRef, repoId)` signature.
7 *
8 * Pipeline:
9 * 1. Walk `git log fromTag..toRef --no-merges` for raw commits.
10 * 2. Cross-reference commits with the `pull_requests` table to enrich
11 * each entry with PR metadata (labels, author, body excerpt).
12 * 3. Bucket commits/PRs by conventional-commit prefix or label into
13 * categories: features, fixes, perf, docs, security, ai_changes, other.
14 * 4. If `ANTHROPIC_API_KEY` is set, ask Claude (claude-sonnet-4-6) to write
15 * a polished, human-readable Markdown changelog from the grouped input.
16 * 5. Fall back to a deterministic grouped list when the key is absent.
17 *
18 * Output format (AI path):
19 *
20 * ## v1.3.0 (since v1.2.0)
21 * **Short release tagline**
22 *
23 * One-to-three sentence summary of what changed and why it matters.
24 *
25 * ### Features
26 * - Add PR preview environments (#42) — @alice
27 *
28 * ### Bug fixes
29 * - Fix race condition in SSE reconnection (#37) — @bob
30 *
31 * _Full changelog_: `v1.2.0...v1.3.0`
32 */
33
34import { generateReleaseNotes } from "./ai-release-notes";
35
36/**
37 * Generate a human-readable Markdown changelog for the commit range
38 * `fromTag..toRef` on `owner/repo`.
39 *
40 * @param owner — repository owner username
41 * @param repo — repository name
42 * @param fromTag — previous tag/ref (e.g. "v1.2.0"); pass empty string for
43 * "all commits reachable from toRef"
44 * @param toRef — target tag/ref (e.g. "HEAD" or "v1.3.0")
45 * @param repoId — database UUID of the repository (for PR cross-reference)
46 * @returns — Markdown string ready to store in `releases.body`
47 */
48export async function generateChangelog(
49 owner: string,
50 repo: string,
51 fromTag: string,
52 toRef: string,
53 repoId: string
54): Promise<string> {
55 const result = await generateReleaseNotes({
56 repositoryId: repoId,
57 fromTag: fromTag.trim() || null,
58 toTag: toRef.trim() || "HEAD",
59 });
60 return result.markdown;
61}
62
63// Re-export helpers that callers may want for testing / introspection.
64export type { ReleaseNotesResult, ReleaseSections, ReleaseSectionKey } from "./ai-release-notes";
65export { classifyPr, bucketPrs, renderSectionsToMarkdown, isSemverTag } from "./ai-release-notes";
066