diff --git a/scripts/generate-release-notes.js b/scripts/generate-release-notes.js index 9efb09d4431..0d6227a6246 100644 --- a/scripts/generate-release-notes.js +++ b/scripts/generate-release-notes.js @@ -34,11 +34,7 @@ export function buildPullRequestQuery(numbers) { pr${index}: pullRequest(number: ${number}) { number body - additions - deletions - changedFiles labels(first: 20) { nodes { name } } - files(first: 40) { nodes { path } } }`, ) .join('\n'); @@ -262,14 +258,7 @@ function compactEntry(entry) { return { number: entry.number, title: entry.title, - body: (entry.body || '').slice(0, 3000), - labels: (entry.labels || []).map((label) => - typeof label === 'string' ? label : label.name, - ), - files: (entry.files || []).slice(0, 40), - additions: entry.additions, - deletions: entry.deletions, - changedFiles: entry.changedFiles, + body: (entry.body || '').slice(0, 700), category: classifyChange(entry), }; } @@ -375,15 +364,10 @@ export function enrichEntries(entries, metadata) { const byNumber = new Map(metadata.map((item) => [item.number, item])); return entries.map((entry) => { const details = byNumber.get(entry.number) || {}; - const files = details.files?.nodes || details.files || []; return { ...entry, body: details.body || '', labels: details.labels?.nodes || details.labels || [], - files: files.map((file) => (typeof file === 'string' ? file : file.path)), - additions: details.additions || 0, - deletions: details.deletions || 0, - changedFiles: details.changedFiles || files.length, }; }); } @@ -468,6 +452,7 @@ export function createOpenAiCompleter({ if (remainingMs <= 0) { throw deadlineError(); } + const attemptStartedAt = Date.now(); try { const response = await fetchImpl(endpoint, { method: 'POST', @@ -495,10 +480,16 @@ export function createOpenAiCompleter({ if (typeof content !== 'string' || !content.trim()) { throw new Error(CONTENT_VALIDATION_ERROR_MESSAGE); } + console.error( + `Model ${request.kind} request succeeded in ${Date.now() - attemptStartedAt}ms (prompt ${prompt.user.length} chars).`, + ); return content; } catch (error) { lastError = error; attempt += 1; + console.error( + `Model ${request.kind} request failed after ${Date.now() - attemptStartedAt}ms (prompt ${prompt.user.length} chars): ${escapeWorkflowCommand(error.message)}`, + ); if (Date.now() >= deadline) { throw deadlineError(); } diff --git a/scripts/tests/generate-release-notes.test.js b/scripts/tests/generate-release-notes.test.js index 9d3edb09737..eb19fb88d04 100644 --- a/scripts/tests/generate-release-notes.test.js +++ b/scripts/tests/generate-release-notes.test.js @@ -39,10 +39,6 @@ const entry = (number, title, labels = []) => ({ author: 'alice', labels, body: '', - files: [], - additions: 1, - deletions: 0, - changedFiles: 1, }); describe('parseGeneratedEntries', () => { @@ -240,6 +236,34 @@ describe('generateAiContent', () => { ]); }); + it('sends only title, a bounded body excerpt, and category to the model', async () => { + const long = { ...entry(1, 'feat: long body'), body: 'x'.repeat(5000) }; + const calls = []; + const complete = async (request) => { + calls.push(request); + if (request.kind === 'summaries') { + return JSON.stringify({ + summaries: request.entries.map((item) => ({ + pr: item.number, + summary: 'Summary.', + })), + }); + } + return JSON.stringify({ highlights: [] }); + }; + + await generateAiContent([long], complete); + + const [payload] = calls[0].entries; + expect(Object.keys(payload).sort()).toEqual([ + 'body', + 'category', + 'number', + 'title', + ]); + expect(payload.body).toHaveLength(700); + }); + it('falls back to original titles for an invalid summary batch', async () => { const entries = [entry(1, 'feat: original'), entry(2, 'fix: original')]; const complete = async (request) => { @@ -359,17 +383,13 @@ describe('enrichEntries', () => { number: 1, body: 'Why it matters.', labels: [{ name: 'type/bug' }], - files: [{ path: 'packages/core/a.ts' }], - additions: 3, - deletions: 2, - changedFiles: 1, }, ]); expect(enriched.map((item) => item.number)).toEqual([2, 1]); expect(enriched[0].body).toBe(''); expect(enriched[1].body).toBe('Why it matters.'); - expect(enriched[1].files).toEqual(['packages/core/a.ts']); + expect(enriched[1].labels).toEqual([{ name: 'type/bug' }]); }); }); @@ -379,7 +399,8 @@ describe('buildPullRequestQuery', () => { expect(query).toContain('pr0: pullRequest(number: 12)'); expect(query).toContain('pr1: pullRequest(number: 8)'); - expect(query).toContain('files(first: 40)'); + expect(query).toContain('labels(first: 20)'); + expect(query).not.toContain('files(first: 40)'); expect(query).not.toContain('pullRequest(number: undefined)'); }); }); @@ -480,7 +501,7 @@ describe('generateReleaseNotes', () => { ' process.exit(0);', '}', "if (args[0] === 'api' && args[1] === 'graphql') {", - " process.stdout.write(JSON.stringify({ data: { repository: { pr0: { number: 1, body: 'Body.', additions: 1, deletions: 0, changedFiles: 1, labels: { nodes: [] }, files: { nodes: [] } } } } }));", + " process.stdout.write(JSON.stringify({ data: { repository: { pr0: { number: 1, body: 'Body.', labels: { nodes: [] } } } } }));", ' process.exit(0);', '}', 'process.exit(1);',