Skip to content

Commit 3562dd4

Browse files
committed
changelog generator wip
1 parent 7c0a710 commit 3562dd4

25 files changed

Lines changed: 16146 additions & 0 deletions

changelog-generator/.env.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Trigger.dev
2+
TRIGGER_SECRET_KEY=tr_dev_xxx
3+
TRIGGER_PROJECT_REF=proj_xxx
4+
5+
# Anthropic
6+
ANTHROPIC_API_KEY=sk-ant-xxx
7+
8+
# GitHub (optional - required for private repos)
9+
GITHUB_TOKEN=ghp_xxx

changelog-generator/.eslintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}

changelog-generator/.gitignore

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# dependencies
2+
/node_modules
3+
/.pnp
4+
.pnp.js
5+
.yarn/install-state.gz
6+
7+
# testing
8+
/coverage
9+
10+
# next.js
11+
/.next/
12+
/out/
13+
14+
# production
15+
/build
16+
17+
# misc
18+
.DS_Store
19+
*.pem
20+
21+
# debug
22+
npm-debug.log*
23+
yarn-debug.log*
24+
yarn-error.log*
25+
26+
# local env files
27+
.env*.local
28+
.env
29+
30+
# vercel
31+
.vercel
32+
33+
# typescript
34+
*.tsbuildinfo
35+
next-env.d.ts

changelog-generator/README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Changelog Generator using Claude Agent SDK and Trigger.dev
2+
3+
Generate intelligent changelogs from GitHub commits using AI with a two-phase approach: scan commits first, then selectively fetch diffs for unclear changes.
4+
5+
## Tech Stack
6+
7+
- **Next.js** – Frontend framework using App Router
8+
- **Claude Agent SDK** – Anthropic's SDK for building AI agents with custom tools
9+
- **Trigger.dev** – Background task orchestration with real-time streaming
10+
- **Octokit** – GitHub API client
11+
12+
## Features
13+
14+
- **Two-phase intelligent analysis**:
15+
1. Claude first lists all commits in the date range
16+
2. For unclear commits ("fix bug", "update"), Claude requests the actual diff
17+
3. Generates grouped changelog with full context
18+
- **Custom MCP tools** – Claude can call `list_commits` and `get_commit_diff` on demand
19+
- **Private repo support** – Optional GitHub token for private repositories
20+
- **Real-time streaming** – Watch the changelog generate live
21+
22+
## How It Works
23+
24+
```
25+
User enters repo URL + date range
26+
27+
API triggers generate-changelog task
28+
29+
Claude Agent SDK with custom GitHub tools:
30+
- list_commits: fetches commit messages via GitHub API
31+
- get_commit_diff: fetches diff for specific commit SHA
32+
33+
Claude explores commits, requests diffs as needed
34+
35+
Generates grouped changelog
36+
37+
Streams to frontend via Trigger.dev Realtime
38+
```
39+
40+
## Setup
41+
42+
1. **Install dependencies**
43+
44+
```bash
45+
npm install
46+
```
47+
48+
2. **Configure environment variables**
49+
50+
```bash
51+
cp .env.example .env
52+
```
53+
54+
Fill in:
55+
- `TRIGGER_SECRET_KEY` – From [Trigger.dev dashboard](https://cloud.trigger.dev/)
56+
- `TRIGGER_PROJECT_REF` – Your project ref (starts with `proj_`)
57+
- `ANTHROPIC_API_KEY` – From [Anthropic Console](https://console.anthropic.com/)
58+
- `GITHUB_TOKEN` (optional) – For private repos, needs `repo` scope
59+
60+
3. **Start development servers**
61+
62+
```bash
63+
# Terminal 1: Next.js
64+
npm run dev
65+
66+
# Terminal 2: Trigger.dev CLI
67+
npx trigger.dev@latest dev
68+
```
69+
70+
4. Open [http://localhost:3000](http://localhost:3000)
71+
72+
## Relevant Files
73+
74+
- `trigger/generate-changelog.ts` – Main task with custom MCP tools for GitHub
75+
- `trigger/changelog-stream.ts` – Stream definition for real-time output
76+
- `app/api/generate-changelog/route.ts` – API endpoint
77+
- `app/response/[runId]/page.tsx` – Streaming display page
78+
79+
## Custom Tools
80+
81+
The task defines two custom MCP tools that Claude can use:
82+
83+
```typescript
84+
// List commits with messages (lightweight)
85+
list_commits({ since: "2024-01-01", until: "2024-02-01" })
86+
87+
// Get full diff for a specific commit (on-demand)
88+
get_commit_diff({ sha: "abc1234" })
89+
```
90+
91+
This two-phase approach minimizes token usage while giving Claude full context when needed.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { runs } from "@trigger.dev/sdk";
2+
import { NextRequest, NextResponse } from "next/server";
3+
4+
export async function POST(request: NextRequest) {
5+
try {
6+
const { runId } = await request.json();
7+
8+
if (!runId || typeof runId !== "string") {
9+
return NextResponse.json(
10+
{ error: "Run ID is required" },
11+
{ status: 400 }
12+
);
13+
}
14+
15+
await runs.cancel(runId);
16+
17+
return NextResponse.json({ success: true });
18+
} catch (error: unknown) {
19+
console.error("Failed to abort task:", error);
20+
const message = error instanceof Error ? error.message : "Failed to abort task";
21+
return NextResponse.json(
22+
{ error: message },
23+
{ status: 500 }
24+
);
25+
}
26+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { tasks } from "@trigger.dev/sdk";
2+
import type { generateChangelog } from "@/trigger/generate-changelog";
3+
import { NextRequest, NextResponse } from "next/server";
4+
5+
export async function POST(request: NextRequest) {
6+
try {
7+
const { repoUrl, startDate, endDate } = await request.json();
8+
9+
// Validate inputs
10+
if (!repoUrl || typeof repoUrl !== "string") {
11+
return NextResponse.json(
12+
{ error: "Repository URL is required" },
13+
{ status: 400 }
14+
);
15+
}
16+
17+
if (!startDate || !endDate) {
18+
return NextResponse.json(
19+
{ error: "Start and end dates are required" },
20+
{ status: 400 }
21+
);
22+
}
23+
24+
// Basic GitHub URL validation
25+
const githubUrlPattern = /^https?:\/\/(www\.)?github\.com\/[\w-]+\/[\w.-]+/;
26+
if (!githubUrlPattern.test(repoUrl)) {
27+
return NextResponse.json(
28+
{ error: "Invalid GitHub URL format" },
29+
{ status: 400 }
30+
);
31+
}
32+
33+
// Trigger the changelog task
34+
const handle = await tasks.trigger<typeof generateChangelog>(
35+
"generate-changelog",
36+
{ repoUrl, startDate, endDate }
37+
);
38+
39+
return NextResponse.json({
40+
runId: handle.id,
41+
accessToken: handle.publicAccessToken,
42+
});
43+
} catch (error: unknown) {
44+
console.error("Failed to trigger generate-changelog task:", error);
45+
const message = error instanceof Error ? error.message : "Failed to start changelog generation";
46+
return NextResponse.json(
47+
{ error: message },
48+
{ status: 500 }
49+
);
50+
}
51+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
:root {
6+
--foreground-rgb: 0, 0, 0;
7+
--background-start-rgb: 214, 219, 220;
8+
--background-end-rgb: 255, 255, 255;
9+
}
10+
11+
@media (prefers-color-scheme: dark) {
12+
:root {
13+
--foreground-rgb: 255, 255, 255;
14+
--background-start-rgb: 0, 0, 0;
15+
--background-end-rgb: 0, 0, 0;
16+
}
17+
}
18+
19+
@layer base {
20+
:root {
21+
--background: 0 0% 100%;
22+
--foreground: 0 0% 3.9%;
23+
--card: 0 0% 100%;
24+
--card-foreground: 0 0% 3.9%;
25+
--popover: 0 0% 100%;
26+
--popover-foreground: 0 0% 3.9%;
27+
--primary: 0 0% 9%;
28+
--primary-foreground: 0 0% 98%;
29+
--secondary: 0 0% 96.1%;
30+
--secondary-foreground: 0 0% 9%;
31+
--muted: 0 0% 96.1%;
32+
--muted-foreground: 0 0% 45.1%;
33+
--accent: 0 0% 96.1%;
34+
--accent-foreground: 0 0% 9%;
35+
--destructive: 0 84.2% 60.2%;
36+
--destructive-foreground: 0 0% 98%;
37+
--border: 0 0% 89.8%;
38+
--input: 0 0% 89.8%;
39+
--ring: 0 0% 3.9%;
40+
--chart-1: 12 76% 61%;
41+
--chart-2: 173 58% 39%;
42+
--chart-3: 197 37% 24%;
43+
--chart-4: 43 74% 66%;
44+
--chart-5: 27 87% 67%;
45+
--radius: 0.5rem;
46+
}
47+
.dark {
48+
--background: 0 0% 3.9%;
49+
--foreground: 0 0% 98%;
50+
--card: 0 0% 3.9%;
51+
--card-foreground: 0 0% 98%;
52+
--popover: 0 0% 3.9%;
53+
--popover-foreground: 0 0% 98%;
54+
--primary: 0 0% 98%;
55+
--primary-foreground: 0 0% 9%;
56+
--secondary: 0 0% 14.9%;
57+
--secondary-foreground: 0 0% 98%;
58+
--muted: 0 0% 14.9%;
59+
--muted-foreground: 0 0% 63.9%;
60+
--accent: 0 0% 14.9%;
61+
--accent-foreground: 0 0% 98%;
62+
--destructive: 0 62.8% 30.6%;
63+
--destructive-foreground: 0 0% 98%;
64+
--border: 0 0% 14.9%;
65+
--input: 0 0% 14.9%;
66+
--ring: 0 0% 83.1%;
67+
--chart-1: 220 70% 50%;
68+
--chart-2: 160 60% 45%;
69+
--chart-3: 30 80% 55%;
70+
--chart-4: 280 65% 60%;
71+
--chart-5: 340 75% 55%;
72+
}
73+
}
74+
75+
@layer base {
76+
* {
77+
@apply border-border;
78+
}
79+
body {
80+
@apply bg-background text-foreground;
81+
}
82+
}

changelog-generator/app/layout.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import "./globals.css";
2+
import type { Metadata } from "next";
3+
import { Inter } from "next/font/google";
4+
5+
const inter = Inter({ subsets: ["latin"] });
6+
7+
export const metadata: Metadata = {
8+
title: "Changelog Generator - AI-powered changelogs from GitHub commits",
9+
description:
10+
"Generate developer-friendly changelogs from any GitHub repository using AI",
11+
};
12+
13+
export default function RootLayout({
14+
children,
15+
}: {
16+
children: React.ReactNode;
17+
}) {
18+
return (
19+
<html lang="en">
20+
<body className={inter.className}>{children}</body>
21+
</html>
22+
);
23+
}

0 commit comments

Comments
 (0)