@@ -14,11 +14,8 @@ const generateChangelogSchema = z.object({
1414 endDate : z . string ( ) ,
1515} ) ;
1616
17- // Create GitHub tools for Claude to use
1817function 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) {
139129export 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 ( / g i t h u b \. c o m \/ ( [ ^ / ] + ) \/ ( [ ^ / ] + ) / ) ;
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