fix(changelog): preserved released sections during auto-fix

Restored the released coding-agent changelog sections that the previous pre-publish fix run flattened into Unreleased. The local checkout only had tags through v15.5.15, so fix-changelogs compared against an old tag and promoted entire newly-added release sections.

- Kept newly-added release-section hunks out of collectPromotableAddedItemLines so the fixer still promotes individual new items added under an existing released section, but does not rewrite whole release blocks.
- Added regression coverage for the release-section case.
- Restored the coding-agent changelog history and kept only the #2582 entry under Unreleased.

Fixes #2582
This commit is contained in:
roboomp
2026-06-14 18:34:41 +00:00
committed by can1357
parent afc4f3c1fe
commit 1d931506d6
3 changed files with 1068 additions and 468 deletions
File diff suppressed because it is too large Load Diff
+21
View File
@@ -23,6 +23,27 @@ describe("collectPromotableAddedItemLines", () => {
expect(lines.get("packages/example/CHANGELOG.md")).toEqual(new Set([12, 33]));
});
it("does not promote items from newly added release sections", () => {
const diff = [
"diff --git a/packages/example/CHANGELOG.md b/packages/example/CHANGELOG.md",
"--- a/packages/example/CHANGELOG.md",
"+++ b/packages/example/CHANGELOG.md",
"@@ -1,0 +1,8 @@",
"+# Changelog",
"+",
"+## [1.0.0] - 2026-01-01",
"+",
"+### Fixed",
"+",
"+- Released fix.",
"+- Another released fix.",
].join("\n");
const lines = collectPromotableAddedItemLines(diff);
expect(lines.get("packages/example/CHANGELOG.md")).toBeUndefined();
});
});
describe("fixChangelogContent", () => {
+10 -2
View File
@@ -425,6 +425,10 @@ export function fixChangelogContent(
function hunkKey(hunk: HunkRef): string {
return `${hunk.path}\0${hunk.index}`;
}
function isAddedReleaseHeadingLine(line: string): boolean {
return line.startsWith("+## [");
}
function itemKey(pathName: string, text: string): string {
return `${pathName}\0${normalizeItemText(text)}`;
@@ -433,11 +437,11 @@ function itemKey(pathName: string, text: string): string {
export function collectPromotableAddedItemLines(diffText: string): Map<string, Set<number>> {
const candidates: AddedItemCandidate[] = [];
const removals: RemovedItemOccurrence[] = [];
const addedReleaseHeadingHunks = new Set<string>();
let currentPath = "";
let oldLine = 0;
let newLine = 0;
let hunkIndex = -1;
for (const rawLine of diffText.replace(/\r\n/g, "\n").split("\n")) {
if (rawLine.startsWith("+++ b/")) {
currentPath = rawLine.slice("+++ b/".length);
@@ -464,6 +468,10 @@ export function collectPromotableAddedItemLines(diffText: string): Map<string, S
const text = rawLine.slice(1);
const hunk = { path: currentPath, index: hunkIndex };
if (marker === "+") {
const hunkKeyValue = hunkKey(hunk);
if (isAddedReleaseHeadingLine(rawLine)) {
addedReleaseHeadingHunks.add(hunkKeyValue);
}
if (isListItemLine(text)) {
candidates.push({
path: currentPath,
@@ -525,8 +533,8 @@ export function collectPromotableAddedItemLines(diffText: string): Map<string, S
const linesByPath = new Map<string, Set<number>>();
for (const candidate of candidates) {
if (candidate.pairedWithRemoval) continue;
const key = hunkKey(candidate.hunk);
if (candidate.pairedWithRemoval || addedReleaseHeadingHunks.has(key)) continue;
const unpairedRemovalCount = unpairedRemovalCountByHunk.get(key) ?? 0;
if (unpairedRemovalCount > 0) {
unpairedRemovalCountByHunk.set(key, unpairedRemovalCount - 1);