chore: updated changelogs + remove summarization marker
This commit is contained in:
@@ -3,15 +3,7 @@ import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { $ } from "bun";
|
||||
import {
|
||||
collectPromotableAddedItemLines,
|
||||
fixChangelogContent,
|
||||
parseChangelog,
|
||||
parseItems,
|
||||
recordSummarizedItemFingerprints,
|
||||
renderChangelog,
|
||||
runChangelogFixer,
|
||||
} from "./fix-changelogs";
|
||||
import { collectPromotableAddedItemLines, fixChangelogContent, runChangelogFixer } from "./fix-changelogs";
|
||||
|
||||
describe("collectPromotableAddedItemLines", () => {
|
||||
it("keeps new changelog item additions while ignoring moves and edits", () => {
|
||||
@@ -163,51 +155,6 @@ describe("fixChangelogContent", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("drops stale source bullets after summarization changes their text", () => {
|
||||
const sourceItem =
|
||||
"- Fixed isolated `task` subagents mutating the parent checkout and stacking parallel task branches ([#6003](https://github.com/can1357/oh-my-pi/issues/6003)).";
|
||||
const document = parseChangelog(
|
||||
["# Changelog", "", "## [Unreleased]", "", "### Fixed", "", sourceItem, ""].join("\n"),
|
||||
);
|
||||
const unreleased = document.sections.find(section => section.title === "Unreleased");
|
||||
if (!unreleased) throw new Error("fixture is missing Unreleased");
|
||||
|
||||
const sourceItems = unreleased.subsections.flatMap(sub => parseItems(sub.lines));
|
||||
const summarizedItem =
|
||||
"- Fixed isolated `task` subagents mutating the parent checkout by detaching the git directory.";
|
||||
unreleased.subsections = [{ title: "Fixed", lines: [{ text: summarizedItem, lineNumber: 0 }] }];
|
||||
recordSummarizedItemFingerprints(document, sourceItems, unreleased);
|
||||
const staleMerge = renderChangelog(document).replace(summarizedItem, `${summarizedItem}\n${sourceItem}`);
|
||||
|
||||
const result = fixChangelogContent(staleMerge, new Set());
|
||||
|
||||
expect(result.droppedReleasedDuplicates).toBe(1);
|
||||
expect(result.content).toContain(summarizedItem);
|
||||
expect(result.content).not.toContain(sourceItem);
|
||||
});
|
||||
|
||||
it("keeps bullets the rewrite preserved verbatim instead of dropping them as consumed", () => {
|
||||
const keptItem =
|
||||
"- Fixed crash when opening the empty settings panel ([#1234](https://github.com/can1357/oh-my-pi/issues/1234)).";
|
||||
const document = parseChangelog(
|
||||
["# Changelog", "", "## [Unreleased]", "", "### Fixed", "", keptItem, ""].join("\n"),
|
||||
);
|
||||
const unreleased = document.sections.find(section => section.title === "Unreleased");
|
||||
if (!unreleased) throw new Error("fixture is missing Unreleased");
|
||||
|
||||
const sourceItems = unreleased.subsections.flatMap(sub => parseItems(sub.lines));
|
||||
// The model returned this bullet unchanged; applyRewrite writes it back.
|
||||
unreleased.subsections = [{ title: "Fixed", lines: [{ text: keptItem, lineNumber: 0 }] }];
|
||||
recordSummarizedItemFingerprints(document, sourceItems, unreleased);
|
||||
const afterRewrite = renderChangelog(document);
|
||||
expect(afterRewrite).not.toContain("changelog-source-item");
|
||||
|
||||
const result = fixChangelogContent(afterRewrite, new Set());
|
||||
|
||||
expect(result.droppedReleasedDuplicates).toBe(0);
|
||||
expect(result.content).toContain(keptItem);
|
||||
});
|
||||
|
||||
it("can recover Unreleased by dropping bullets known to be historically released", () => {
|
||||
const content = [
|
||||
"# Changelog",
|
||||
|
||||
+10
-67
@@ -7,7 +7,6 @@ const CHANGELOG_GLOB = "packages/*/CHANGELOG.md";
|
||||
const ORDERED_SECTION_TITLES = ["Breaking Changes", "Added", "Changed", "Fixed", "Removed"] as const;
|
||||
const CHANGELOG_BASELINE_REF = "refs/clog";
|
||||
const CHANGELOG_BASELINE_NAME = "clog";
|
||||
const SUMMARIZED_ITEM_MARKER = /^<!-- changelog-source-item sha256:([0-9a-f]{64}) -->$/;
|
||||
|
||||
export interface NumberedLine {
|
||||
text: string;
|
||||
@@ -278,58 +277,6 @@ function itemTextKey(itemLines: readonly string[]): string {
|
||||
return trimBlankLines(itemLines).join("\n");
|
||||
}
|
||||
|
||||
function itemFingerprint(itemLines: readonly string[]): string {
|
||||
const text = itemTextKey(itemLines);
|
||||
if (!text) return "";
|
||||
return new Bun.CryptoHasher("sha256").update(text).digest("hex");
|
||||
}
|
||||
|
||||
function collectRecordedItemFingerprints(lines: readonly NumberedLine[], keys: Set<string>): void {
|
||||
for (const line of lines) {
|
||||
const fingerprint = line.text.match(SUMMARIZED_ITEM_MARKER)?.[1];
|
||||
if (fingerprint) keys.add(fingerprint);
|
||||
}
|
||||
}
|
||||
|
||||
function addItemKeys(keys: Set<string>, itemLines: readonly string[]): void {
|
||||
const text = itemTextKey(itemLines);
|
||||
if (text) keys.add(text);
|
||||
const fingerprint = itemFingerprint(itemLines);
|
||||
if (fingerprint) keys.add(fingerprint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records fingerprints for source items consumed by summarization. Called
|
||||
* after the rewrite is applied: items whose exact text survives in the
|
||||
* rewritten section are skipped — recording those would make the next fixer
|
||||
* run treat the still-valid Unreleased bullet as already consumed and drop it.
|
||||
*/
|
||||
export function recordSummarizedItemFingerprints(
|
||||
document: ChangelogDocument,
|
||||
sourceItems: readonly ParsedItem[],
|
||||
rewrittenSection: ReleaseSection,
|
||||
): void {
|
||||
const surviving = new Set<string>();
|
||||
for (const subsection of rewrittenSection.subsections) {
|
||||
for (const item of parseItems(subsection.lines)) {
|
||||
const text = itemTextKey(item.lines);
|
||||
if (text) surviving.add(text);
|
||||
}
|
||||
}
|
||||
const recorded = new Set<string>();
|
||||
collectRecordedItemFingerprints(document.prefixLines, recorded);
|
||||
for (const item of sourceItems) {
|
||||
if (surviving.has(itemTextKey(item.lines))) continue;
|
||||
const fingerprint = itemFingerprint(item.lines);
|
||||
if (!fingerprint || recorded.has(fingerprint)) continue;
|
||||
document.prefixLines.push({
|
||||
text: `<!-- changelog-source-item sha256:${fingerprint} -->`,
|
||||
lineNumber: 0,
|
||||
});
|
||||
recorded.add(fingerprint);
|
||||
}
|
||||
}
|
||||
|
||||
function subsectionHasItem(subsection: Subsection, itemLines: readonly string[]): boolean {
|
||||
const wanted = itemTextKey(itemLines);
|
||||
if (!wanted) return true;
|
||||
@@ -341,12 +288,12 @@ function subsectionHasItem(subsection: Subsection, itemLines: readonly string[])
|
||||
|
||||
function collectReleasedItemKeys(document: ChangelogDocument): Set<string> {
|
||||
const keys = new Set<string>();
|
||||
collectRecordedItemFingerprints(document.prefixLines, keys);
|
||||
for (const section of document.sections) {
|
||||
if (section.title === "Unreleased") continue;
|
||||
for (const subsection of section.subsections) {
|
||||
for (const item of parseItems(subsection.lines)) {
|
||||
addItemKeys(keys, item.lines);
|
||||
const key = itemTextKey(item.lines);
|
||||
if (key) keys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,10 +301,11 @@ function collectReleasedItemKeys(document: ChangelogDocument): Set<string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops items from [Unreleased] that already appeared in a released section or
|
||||
* were consumed by changelog summarization. Summarization records source
|
||||
* fingerprints before replacing the text, so stale branches cannot reintroduce
|
||||
* the original wording. Runs before promotion and only mutates Unreleased.
|
||||
* Drop items from [Unreleased] that already appear verbatim in a released
|
||||
* section — the residue of a release that copied [Unreleased] into the new
|
||||
* version section without clearing it. The released copy is authoritative, so
|
||||
* the Unreleased duplicate is removed. Runs before promotion (while parse line
|
||||
* numbers are still real) and only ever mutates the Unreleased section.
|
||||
*/
|
||||
function dropUnreleasedDuplicatesOfReleased(
|
||||
document: ChangelogDocument,
|
||||
@@ -371,11 +319,7 @@ function dropUnreleasedDuplicatesOfReleased(
|
||||
|
||||
let dropped = 0;
|
||||
for (const subsection of unreleased.subsections) {
|
||||
const duplicates = parseItems(subsection.lines).filter(item => {
|
||||
const text = itemTextKey(item.lines);
|
||||
const fingerprint = itemFingerprint(item.lines);
|
||||
return releasedKeys.has(text) || releasedKeys.has(fingerprint);
|
||||
});
|
||||
const duplicates = parseItems(subsection.lines).filter(item => releasedKeys.has(itemTextKey(item.lines)));
|
||||
if (duplicates.length === 0) continue;
|
||||
const linesToRemove = lineRangeSet(duplicates);
|
||||
subsection.lines = subsection.lines.filter(line => !linesToRemove.has(line.lineNumber));
|
||||
@@ -860,12 +804,11 @@ async function collectHistoricalReleaseRecovery(
|
||||
if (recovery.sectionsByTitle.get(section.title) !== undefined) {
|
||||
for (const subsection of section.subsections) {
|
||||
for (const item of parseItems(subsection.lines)) {
|
||||
addItemKeys(recovery.itemKeys, item.lines);
|
||||
recovery.itemKeys.add(itemTextKey(item.lines));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (recovery) collectRecordedItemFingerprints(document.prefixLines, recovery.itemKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,7 +929,7 @@ function usage(): string {
|
||||
"Usage: bun scripts/fix-changelogs.ts [--dry-run|--check] [--since <tag>] [--recover] [--pin]",
|
||||
"",
|
||||
"Moves changelog items added since the baseline from released sections into [Unreleased],",
|
||||
"drops [Unreleased] items already released or consumed by changelog summarization, removes",
|
||||
"drops [Unreleased] items that already appear verbatim in a released section, removes",
|
||||
"blank separators between adjacent bullet items, then removes duplicate or empty",
|
||||
"### category headings.",
|
||||
"",
|
||||
|
||||
@@ -51,7 +51,6 @@ import {
|
||||
parseChangelog,
|
||||
parseItems,
|
||||
type ReleaseSection,
|
||||
recordSummarizedItemFingerprints,
|
||||
renderChangelog,
|
||||
resolveRepoRoot,
|
||||
} from "./fix-changelogs";
|
||||
@@ -335,10 +334,8 @@ async function run(options: RunOptions): Promise<RunResult> {
|
||||
.replace(/^## \[Unreleased\]\n?/, "")
|
||||
.trim();
|
||||
|
||||
const sourceItems = section.subsections.flatMap(sub => parseItems(sub.lines));
|
||||
const rewritten = await requestRewrite(model, changelogPath, unreleasedBody);
|
||||
applyRewrite(section, rewritten);
|
||||
recordSummarizedItemFingerprints(document, sourceItems, section);
|
||||
const next = renderChangelog(document);
|
||||
if (next === content) continue;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user