Skip to content

Commit ccf7ac8

Browse files
committed
Refactored generate-changelog
1 parent 199cb5f commit ccf7ac8

1 file changed

Lines changed: 55 additions & 142 deletions

File tree

changelog-generator/trigger/generate-changelog.ts

Lines changed: 55 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,8 @@ const generateChangelogSchema = z.object({
1414
endDate: z.string(),
1515
});
1616

17-
// Create GitHub tools for Claude to use
1817
function createGitHubTools(owner: string, repo: string) {
19-
const octokit = new Octokit({
20-
auth: process.env.GITHUB_TOKEN,
21-
});
18+
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
2219

2320
return createSdkMcpServer({
2421
name: "github",
@@ -34,31 +31,29 @@ function createGitHubTools(owner: string, repo: string) {
3431
async (args) => {
3532
const commits: string[] = [];
3633
let page = 1;
37-
const perPage = 100;
3834

39-
while (true) {
40-
const response = await octokit.rest.repos.listCommits({
35+
while (commits.length < 300) {
36+
const { data } = await octokit.rest.repos.listCommits({
4137
owner,
4238
repo,
4339
since: args.since,
4440
until: args.until,
45-
per_page: perPage,
41+
per_page: 100,
4642
page,
4743
});
4844

49-
if (response.data.length === 0) break;
45+
if (data.length === 0) break;
5046

51-
for (const commit of response.data) {
47+
for (const commit of data) {
5248
const sha = commit.sha.substring(0, 7);
5349
const date = commit.commit.author?.date?.split("T")[0] || "";
5450
const author = commit.commit.author?.name || "Unknown";
5551
const message = commit.commit.message.split("\n")[0];
5652
commits.push(`[${sha}] ${date} (${author}): ${message}`);
5753
}
5854

59-
if (response.data.length < perPage) break;
55+
if (data.length < 100) break;
6056
page++;
61-
if (commits.length >= 300) break;
6257
}
6358

6459
metadata.set("commitCount", commits.length);
@@ -80,54 +75,49 @@ function createGitHubTools(owner: string, repo: string) {
8075
"get_commit_diff",
8176
"Get the full diff/patch for a specific commit. Use this to understand what code actually changed when the commit message is unclear or you need more context.",
8277
{
83-
sha: z
84-
.string()
85-
.describe("The commit SHA (short or full) to get diff for"),
78+
sha: z.string().describe("The commit SHA (short or full)"),
8679
},
8780
async (args) => {
8881
try {
89-
const response = await octokit.rest.repos.getCommit({
82+
const { data: commit } = await octokit.rest.repos.getCommit({
9083
owner,
9184
repo,
9285
ref: args.sha,
9386
});
9487

95-
const commit = response.data;
9688
const files = commit.files || [];
97-
98-
let diffOutput = `Commit: ${commit.sha.substring(0, 7)}\n`;
99-
diffOutput += `Author: ${commit.commit.author?.name}\n`;
100-
diffOutput += `Date: ${commit.commit.author?.date}\n`;
101-
diffOutput += `Message: ${commit.commit.message}\n\n`;
102-
diffOutput += `Files changed: ${files.length}\n`;
103-
diffOutput += `Additions: ${
104-
commit.stats?.additions || 0
105-
}, Deletions: ${commit.stats?.deletions || 0}\n\n`;
89+
const lines = [
90+
`Commit: ${commit.sha.substring(0, 7)}`,
91+
`Author: ${commit.commit.author?.name}`,
92+
`Date: ${commit.commit.author?.date}`,
93+
`Message: ${commit.commit.message}`,
94+
"",
95+
`Files changed: ${files.length}`,
96+
`+${commit.stats?.additions || 0} -${
97+
commit.stats?.deletions || 0
98+
}`,
99+
"",
100+
];
106101

107102
for (const file of files) {
108-
diffOutput += `--- ${file.filename} (${file.status}) ---\n`;
103+
lines.push(`--- ${file.filename} (${file.status}) ---`);
109104
if (file.patch) {
110-
// Truncate very large patches
111105
const patch = file.patch.length > 2000
112106
? file.patch.substring(0, 2000) + "\n... (truncated)"
113107
: file.patch;
114-
diffOutput += patch + "\n\n";
108+
lines.push(patch, "");
115109
}
116110
}
117111

118112
return {
119-
content: [{ type: "text" as const, text: diffOutput }],
113+
content: [{ type: "text" as const, text: lines.join("\n") }],
120114
};
121115
} catch (error) {
116+
const msg = error instanceof Error
117+
? error.message
118+
: "Unknown error";
122119
return {
123-
content: [
124-
{
125-
type: "text" as const,
126-
text: `Error fetching commit ${args.sha}: ${
127-
error instanceof Error ? error.message : "Unknown error"
128-
}`,
129-
},
130-
],
120+
content: [{ type: "text" as const, text: `Error: ${msg}` }],
131121
};
132122
}
133123
},
@@ -139,29 +129,21 @@ function createGitHubTools(owner: string, repo: string) {
139129
export const generateChangelog = schemaTask({
140130
id: "generate-changelog",
141131
schema: generateChangelogSchema,
142-
maxDuration: 300, // 5 minutes for agent exploration
132+
maxDuration: 300,
143133
run: async ({ repoUrl, startDate, endDate }, { signal }) => {
144134
const abortController = new AbortController();
145135
signal.addEventListener("abort", () => abortController.abort());
146136

147-
metadata.set("status", "Starting...");
148-
metadata.set("progress", 10);
149-
150137
// Parse repo URL
151138
const match = repoUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
152-
if (!match) {
153-
throw new Error("Invalid GitHub repository URL");
154-
}
139+
if (!match) throw new Error("Invalid GitHub repository URL");
140+
155141
const [, owner, repo] = match;
156142
const repoName = `${owner}/${repo.replace(".git", "")}`;
157143
metadata.set("repository", repoName);
158144

159-
// Create GitHub tools for this repo
160145
const githubServer = createGitHubTools(owner, repo.replace(".git", ""));
161146

162-
metadata.set("status", "Analyzing commits...");
163-
metadata.set("progress", 20);
164-
165147
const promptContent =
166148
`You are a changelog writer analyzing the ${repoName} repository.
167149
@@ -285,99 +267,50 @@ Output ONLY the final changelog, no explanations or preamble.`;
285267
});
286268

287269
const startTime = Date.now();
288-
289-
// Initialize structured agent state
290-
metadata.set("agent", {
291-
phase: "exploring",
292-
turns: 0,
293-
toolCalls: [],
294-
diffsInvestigated: [],
295-
startedAt: new Date().toISOString(),
296-
});
297-
metadata.set("status", "Agent exploring commits...");
298-
metadata.set("progress", 40);
299-
300-
// Local state for tracking
301-
const toolCalls: Array<{
302-
tool: string;
303-
input: string;
304-
timestamp: string;
305-
}> = [];
306-
let turnCount = 0;
270+
const toolCalls: { tool: string; input: string }[] = [];
307271
const diffsInvestigated: string[] = [];
272+
let turns = 0;
273+
274+
const updateAgent = (phase: string) => {
275+
metadata.set("agent", { phase, turns, toolCalls, diffsInvestigated });
276+
};
277+
278+
updateAgent("exploring");
308279

309-
// Stream the response
310280
const { waitUntilComplete } = changelogStream.writer({
311281
execute: async ({ write }) => {
312282
for await (const message of result) {
313-
// Track turns
314283
if (message.type === "assistant") {
315-
turnCount++;
316-
metadata.set("agent", {
317-
phase: "exploring",
318-
turns: turnCount,
319-
toolCalls: [...toolCalls],
320-
diffsInvestigated: [...diffsInvestigated],
321-
startedAt: new Date(startTime).toISOString(),
322-
});
284+
turns++;
285+
updateAgent("exploring");
323286
}
324287

325-
// Log tool calls with details
326288
if (message.type === "assistant" && message.message?.content) {
327289
for (const block of message.message.content) {
328290
if (block.type === "tool_use") {
329-
const toolCall = {
330-
tool: block.name.replace("mcp__github__", ""),
331-
input: JSON.stringify(block.input),
332-
timestamp: new Date().toISOString(),
333-
};
334-
toolCalls.push(toolCall);
335-
336-
if (block.name === "mcp__github__list_commits") {
337-
metadata.set("status", "Fetching commit list...");
338-
metadata.set("agent", {
339-
phase: "fetching_commits",
340-
turns: turnCount,
341-
toolCalls: [...toolCalls],
342-
diffsInvestigated: [...diffsInvestigated],
343-
startedAt: new Date(startTime).toISOString(),
344-
});
345-
} else if (block.name === "mcp__github__get_commit_diff") {
346-
const sha = (block.input as { sha?: string })?.sha || "unknown";
291+
const tool = block.name.replace("mcp__github__", "");
292+
const input = JSON.stringify(block.input);
293+
toolCalls.push({ tool, input });
294+
295+
if (tool === "list_commits") {
296+
updateAgent("fetching_commits");
297+
} else if (tool === "get_commit_diff") {
298+
const sha = (block.input as { sha?: string })?.sha ||
299+
"unknown";
347300
diffsInvestigated.push(sha);
348-
metadata.set(
349-
"status",
350-
`Investigating commit ${sha} (${diffsInvestigated.length} checked)`,
351-
);
352-
metadata.set("agent", {
353-
phase: "investigating_diffs",
354-
turns: turnCount,
355-
toolCalls: [...toolCalls],
356-
diffsInvestigated: [...diffsInvestigated],
357-
currentDiff: sha,
358-
startedAt: new Date(startTime).toISOString(),
359-
});
301+
updateAgent("investigating_diffs");
360302
}
361303
}
362304
}
363305
}
364306

365-
// Stream text deltas
366307
if (message.type === "stream_event") {
367308
const event = message.event;
368309
if (
369310
event.type === "content_block_delta" &&
370311
event.delta.type === "text_delta"
371312
) {
372-
metadata.set("status", "Writing changelog...");
373-
metadata.set("progress", 80);
374-
metadata.set("agent", {
375-
phase: "writing",
376-
turns: turnCount,
377-
toolCalls: [...toolCalls],
378-
diffsInvestigated: [...diffsInvestigated],
379-
startedAt: new Date(startTime).toISOString(),
380-
});
313+
updateAgent("writing");
381314
write(event.delta.text);
382315
}
383316
}
@@ -387,31 +320,11 @@ Output ONLY the final changelog, no explanations or preamble.`;
387320

388321
await waitUntilComplete();
389322

390-
const duration = Date.now() - startTime;
391-
392-
// Final summary
393-
metadata.set("status", "Completed");
394-
metadata.set("progress", 100);
395-
metadata.set("agent", {
396-
phase: "completed",
397-
turns: turnCount,
398-
toolCalls: [...toolCalls],
399-
diffsInvestigated: [...diffsInvestigated],
400-
startedAt: new Date(startTime).toISOString(),
401-
completedAt: new Date().toISOString(),
402-
durationMs: duration,
403-
});
323+
updateAgent("completed");
404324
metadata.set("summary", {
405-
diffsChecked: diffsInvestigated.length,
406-
agentTurns: turnCount,
407-
durationSec: Math.round(duration / 1000),
325+
durationSec: Math.round((Date.now() - startTime) / 1000),
408326
});
409327

410-
return {
411-
repoName,
412-
startDate,
413-
endDate,
414-
status: "completed",
415-
};
328+
return { repoName, startDate, endDate };
416329
},
417330
});

0 commit comments

Comments
 (0)