mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 00:55:16 -07:00
fix(changelog): summarize prerelease notes as net outcome
This commit is contained in:
@@ -374,6 +374,74 @@ test('writeChangelogArtifacts renders breaking changes section above type sectio
|
||||
}
|
||||
});
|
||||
|
||||
test('writeChangelogArtifacts prompts Claude to summarize the final stable outcome instead of prerelease churn', async () => {
|
||||
const { writeChangelogArtifacts } = await loadModule();
|
||||
const workspace = createWorkspace('stable-outcome-prompt');
|
||||
const projectRoot = path.join(workspace, 'SubMiner');
|
||||
|
||||
fs.mkdirSync(projectRoot, { recursive: true });
|
||||
fs.mkdirSync(path.join(projectRoot, 'changes'), { recursive: true });
|
||||
fs.writeFileSync(path.join(projectRoot, 'CHANGELOG.md'), '# Changelog\n', 'utf8');
|
||||
fs.writeFileSync(
|
||||
path.join(projectRoot, 'changes', '001.md'),
|
||||
[
|
||||
'type: added',
|
||||
'area: config',
|
||||
'',
|
||||
'- Added a dedicated Config window with launcher entry points.',
|
||||
].join('\n'),
|
||||
'utf8',
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(projectRoot, 'changes', '002.md'),
|
||||
[
|
||||
'type: changed',
|
||||
'area: config',
|
||||
'breaking: true',
|
||||
'',
|
||||
'- Renamed the Config window to Settings window and changed the launcher entry point to `subminer settings`.',
|
||||
].join('\n'),
|
||||
'utf8',
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(projectRoot, 'changes', '003.md'),
|
||||
[
|
||||
'type: fixed',
|
||||
'area: config',
|
||||
'',
|
||||
'- Fixed Settings window search and live subtitle CSS saves.',
|
||||
].join('\n'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
try {
|
||||
const stub = defaultStubClaude();
|
||||
writeChangelogArtifacts({
|
||||
cwd: projectRoot,
|
||||
version: '0.12.0',
|
||||
date: '2026-05-24',
|
||||
deps: { runClaude: stub.runClaude },
|
||||
});
|
||||
|
||||
const prompts = stub.calls.map((call) => call.input);
|
||||
assert.equal(prompts.length, 2, 'expected changelog and release-notes prompts');
|
||||
for (const prompt of prompts) {
|
||||
assert.match(prompt, /Treat the fragment list as one cumulative release outcome/);
|
||||
assert.match(
|
||||
prompt,
|
||||
/only if the final release requires action from users upgrading from the previous stable release/,
|
||||
);
|
||||
assert.match(prompt, /Config window.*Settings window/s);
|
||||
assert.match(
|
||||
prompt,
|
||||
/Multiple fixes within the same prerelease cycle should collapse into one current-state bullet/,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
fs.rmSync(workspace, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('verifyChangelogFragments rejects invalid metadata', async () => {
|
||||
const { verifyChangelogFragments } = await loadModule();
|
||||
const workspace = createWorkspace('lint-invalid');
|
||||
@@ -575,6 +643,74 @@ test('writePrereleaseNotesForVersion reuses existing prerelease notes when addin
|
||||
}
|
||||
});
|
||||
|
||||
test('writePrereleaseNotesForVersion prompts Claude to revise stale prerelease bullets instead of appending fix churn', async () => {
|
||||
const { writePrereleaseNotesForVersion } = await loadModule();
|
||||
const workspace = createWorkspace('prerelease-net-outcome-prompt');
|
||||
const projectRoot = path.join(workspace, 'SubMiner');
|
||||
const existingNotes = [
|
||||
'> This is a prerelease build for testing. Stable changelog and docs-site updates remain pending until the final stable release.',
|
||||
'',
|
||||
'## Highlights',
|
||||
'### Added',
|
||||
'- Config Window: Previous beta entry.',
|
||||
'',
|
||||
'## Installation',
|
||||
'',
|
||||
'See the README and docs/installation guide for full setup steps.',
|
||||
'',
|
||||
'## Assets',
|
||||
'',
|
||||
'- Linux: `SubMiner.AppImage`',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
fs.mkdirSync(path.join(projectRoot, 'changes'), { recursive: true });
|
||||
fs.mkdirSync(path.join(projectRoot, 'release'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(projectRoot, 'package.json'),
|
||||
JSON.stringify({ name: 'subminer', version: '0.12.0-beta.2' }, null, 2),
|
||||
'utf8',
|
||||
);
|
||||
fs.writeFileSync(path.join(projectRoot, 'release', 'prerelease-notes.md'), existingNotes, 'utf8');
|
||||
fs.writeFileSync(
|
||||
path.join(projectRoot, 'changes', '001.md'),
|
||||
[
|
||||
'type: changed',
|
||||
'area: config',
|
||||
'breaking: true',
|
||||
'',
|
||||
'- Renamed the Config window to Settings window.',
|
||||
].join('\n'),
|
||||
'utf8',
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(projectRoot, 'changes', '002.md'),
|
||||
['type: fixed', 'area: config', '', '- Fixed Settings window search.'].join('\n'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
try {
|
||||
const stub = recordingRunClaude(() => '### Added\n- Settings Window: Current beta state.');
|
||||
writePrereleaseNotesForVersion({
|
||||
cwd: projectRoot,
|
||||
version: '0.12.0-beta.2',
|
||||
deps: { runClaude: stub.runClaude },
|
||||
});
|
||||
|
||||
assert.equal(stub.calls.length, 1, 'prerelease should issue exactly one Claude call');
|
||||
const prompt = stub.calls[0]!.input;
|
||||
assert.match(prompt, /EXISTING PRERELEASE NOTES/);
|
||||
assert.match(prompt, /Existing prerelease notes are a baseline, not an immutable changelog/);
|
||||
assert.match(prompt, /replace stale beta or RC wording/);
|
||||
assert.match(
|
||||
prompt,
|
||||
/Multiple fixes within the same prerelease cycle should collapse into one current-state bullet/,
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(workspace, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('writePrereleaseNotesForVersion supports rc prereleases', async () => {
|
||||
const { writePrereleaseNotesForVersion } = await loadModule();
|
||||
const workspace = createWorkspace('prerelease-rc-notes');
|
||||
|
||||
@@ -235,6 +235,13 @@ const POLISH_PROMPT_INSTRUCTIONS = `You are formatting a software release change
|
||||
|
||||
You will receive a list of FRAGMENT entries below. Each fragment has metadata (type, area, breaking) and one or more bullet points written by the engineer who shipped that change. Your job is to merge, dedupe, and rewrite these fragments into a polished, user-facing release body.
|
||||
|
||||
## Release Outcome Rules
|
||||
|
||||
- Treat the fragment list as one cumulative release outcome, not a chronological log of beta/RC churn.
|
||||
- Put a fragment in ### Breaking Changes only if the final release requires action from users upgrading from the previous stable release. A breaking: true marker is a warning to preserve and evaluate the substance, not an automatic section choice.
|
||||
- If a breaking or fixed fragment only changes behavior introduced by another pending fragment in the same release cycle, merge it into the final Added or Changed bullet. Example: if fragments first add a Config window and later rename or fix it as a Settings window, output one Settings Window bullet under Added, not separate Config window, Breaking Changes, or Fixed bullets.
|
||||
- Multiple fixes within the same prerelease cycle should collapse into one current-state bullet that describes the final behavior.
|
||||
|
||||
## Output Rules
|
||||
|
||||
1. Output Markdown ONLY. No preamble, no commentary, no closing remarks. Start directly with the first section heading.
|
||||
@@ -258,7 +265,7 @@ You will receive a list of FRAGMENT entries below. Each fragment has metadata (t
|
||||
- Be written in user-facing language. Drop implementation jargon, internal class names, file paths, and PR numbers.
|
||||
- Be merged with related bullets when possible. If five fragments all touch Windows overlay z-order/focus/restore, write one or two bullets that summarize the overall improvement instead of five.
|
||||
- Drop bullets that only describe PR housekeeping, CodeRabbit follow-ups, or test-only changes that don't affect users.
|
||||
- Preserve the substance of every breaking change in ### Breaking Changes. Do not soften or omit them.
|
||||
- Preserve the substance of breaking changes that remain breaking after applying the Release Outcome Rules. Do not soften or omit them.
|
||||
5. Do not invent features. Every bullet must be grounded in the input fragments.
|
||||
6. Do not include the version heading (## v...) — that wrapper is added by the caller.
|
||||
|
||||
@@ -371,7 +378,7 @@ function polishFragmentsWithClaude(
|
||||
? [
|
||||
'## Existing Prerelease Notes',
|
||||
'',
|
||||
'The input includes EXISTING PRERELEASE NOTES before the fragment list. Reuse those highlight bullets as the baseline, preserve their meaning and wording where possible, then merge in only new or changed fragment material. Deduplicate instead of restating existing bullets. Output only the final highlights body using the section headings above; do not include the prerelease disclaimer, Installation, or Assets sections.',
|
||||
'The input includes EXISTING PRERELEASE NOTES before the fragment list. Existing prerelease notes are a baseline, not an immutable changelog. Reuse reviewed highlight bullets when they still describe the current outcome, but replace stale beta or RC wording when new fragments supersede it. Merge in only new or changed fragment material, and deduplicate instead of restating existing bullets. Output only the final highlights body using the section headings above; do not include the prerelease disclaimer, Installation, or Assets sections.',
|
||||
'',
|
||||
].join('\n')
|
||||
: '';
|
||||
|
||||
Reference in New Issue
Block a user