fix(ci): compact commit list + bigger token budget for release announce [skip ci]

The v3.0.2 announce (PR #263, 45 commits) never reached Discord: the
changelog prompt carried every commit body (52 KB), deepseek-v4-flash
spent its whole 6000-token budget reasoning, returned empty content and
the guard aborted the run before posting.

Past 8000 chars of commit detail we now send hash + subject only (52916
-> 3699 chars on that range), raise the budgets to 32000/16000, and retry
once with double when content still comes back empty. Also log
finish_reason and completion_tokens so the next empty answer is
diagnosable from the run log alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Montero 2026-08-06 01:41:29 +02:00
parent 588c356e7e
commit 388b013ac4
1 changed files with 41 additions and 10 deletions

View File

@ -59,8 +59,10 @@ jobs:
// deepseek-v4-flash is a REASONING model: max_tokens caps the
// reasoning trace + the visible answer TOGETHER, so a stingy budget
// makes `content` come back empty (which once made us POST an empty
// Discord message). Give it generous budgets.
async function callDeepSeek(prompt, maxTokens, temperature = 0.3) {
// Discord message, and once killed the v3.0.2 announce outright).
// Give it generous budgets, and retry with double when the model
// spends the whole budget reasoning.
async function deepSeekRequest(prompt, maxTokens, temperature) {
const apiKey = process.env.DAVEAGENT_API_KEY;
if (!apiKey) throw new Error('DAVEAGENT_API_KEY not set');
const res = await fetch('https://api.deepseek.com/v1/chat/completions', {
@ -81,7 +83,21 @@ jobs:
throw new Error(`DeepSeek ${res.status}: ${err}`);
}
const data = await res.json();
return (data.choices?.[0]?.message?.content ?? '').trim();
const choice = data.choices?.[0] ?? {};
console.log(
`DeepSeek: max_tokens=${maxTokens} finish_reason=${choice.finish_reason} ` +
`completion_tokens=${data.usage?.completion_tokens ?? '?'}`
);
return (choice.message?.content ?? '').trim();
}
async function callDeepSeek(prompt, maxTokens, temperature = 0.3) {
let content = await deepSeekRequest(prompt, maxTokens, temperature);
if (!content) {
console.log('Empty content — retrying once with double the token budget');
content = await deepSeekRequest(prompt, maxTokens * 2, temperature);
}
return content;
}
// ── Version (read from release's package.json) ────────────────────
@ -120,15 +136,27 @@ jobs:
console.log(`Event: ${context.eventName} PR: #${prNumber} "${prTitle}"`);
console.log(`Range: ${baseSha.slice(0,7)}..${headSha.slice(0,7)} (${sourceBranch} → release)`);
const commits = git(
// Full detail (subject + BODY + author) reads best for a small
// release, but a 45-commit range blows the prompt up and the model
// burns its whole budget reasoning over it. Past the threshold,
// drop the bodies and send subjects only.
const MAX_COMMIT_CHARS = 8000;
const commitsFull = git(
`log ${baseSha}..${headSha} --pretty=format:"%h||%s||%b||%an|||" --no-merges`
) || git(`log -50 --pretty=format:"%h||%s||%b||%an|||" --no-merges`);
const commitLines = git(
`log ${baseSha}..${headSha} --pretty=format:"%h %s" --no-merges`
) || git(`log -50 --pretty=format:"%h %s" --no-merges`);
const commitCount = commitLines.split('\n').filter(Boolean).length;
const compact = commitsFull.length > MAX_COMMIT_CHARS;
// Subjects alone can still be huge on a monster range — hard cap.
const commits = (compact ? commitLines : commitsFull).slice(0, 4 * MAX_COMMIT_CHARS);
const changedFiles = git(`diff --name-only ${baseSha}..${headSha}`)
.split('\n').filter(Boolean);
console.log(`Commits found: ${commitLines.split('\n').filter(Boolean).length}`);
console.log(
`Commits found: ${commitCount} — sending ${compact ? 'SUBJECTS ONLY' : 'full detail'} ` +
`(${commits.length} chars, full detail would be ${commitsFull.length})`
);
console.log(`Files changed: ${changedFiles.length}`);
// ──────────────────────────────────────────────────────────────────
@ -142,7 +170,7 @@ jobs:
PR DESCRIPTION: ${prBody}
SOURCE BRANCH: ${sourceBranch}
GIT COMMITS (hash||subject||body||author):
GIT COMMITS (${compact ? 'hash subject' : 'hash||subject||body||author'}):
${commits}
Generate a changelog entry using EXACTLY this format:
@ -167,8 +195,11 @@ jobs:
6. Only include sections that have real content
7. Output ONLY the changelog entry block, nothing else`;
console.log('Generating CHANGELOG entry...');
const changelogEntry = await callDeepSeek(changelogPrompt, 6000, 0.3);
console.log(`Generating CHANGELOG entry... (prompt: ${changelogPrompt.length} chars)`);
// 16000 was measured to still hit finish_reason=length (all of it
// spent reasoning, empty content) on a 45-commit range. 32000 lands
// comfortably; the retry doubles to 64000, which the API accepts.
const changelogEntry = await callDeepSeek(changelogPrompt, 32000, 0.3);
if (!changelogEntry) {
core.setFailed('CHANGELOG generation returned empty content — aborting before any side effects');
return;
@ -189,7 +220,7 @@ jobs:
${changelogEntry}
RECENT COMMITS (for additional context):
${commitLines}
${commitLines.slice(0, MAX_COMMIT_CHARS)}
STYLE EXAMPLES:
@ -232,7 +263,7 @@ jobs:
- Write in English`;
console.log('Generating Discord announcement...');
let announcement = await callDeepSeek(announcementPrompt, 4000, 0.7);
let announcement = await callDeepSeek(announcementPrompt, 16000, 0.7);
// Limpiar markdown residual
announcement = announcement