Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions packages/coding-agent/src/modes/interactive/interactive-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ import type { TruncationResult } from "../../core/tools/truncate.ts";
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.ts";
import { getUsageCostBreakdown } from "../../core/usage-totals.ts";
import {
formatStartupChangelogPreviewLines,
getChangelogPath,
getNewEntries,
getStartupChangelogPreviewLines,
normalizeChangelogLinks,
parseChangelog,
selectStartupChangelogEntries,
} from "../../utils/changelog.ts";
import { copyToClipboard, readClipboardText } from "../../utils/clipboard.ts";
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.ts";
Expand Down Expand Up @@ -1188,7 +1188,11 @@ export class InteractiveMode {
}

private getStartupWhatsNewLines(): string[] {
const previewLines = getStartupChangelogPreviewLines(getChangelogPath());
if (!this.changelogMarkdown) {
return [];
}

const previewLines = formatStartupChangelogPreviewLines(this.changelogMarkdown);
if (previewLines.length === 0) {
return [];
}
Expand All @@ -1215,22 +1219,16 @@ export class InteractiveMode {
const lastVersion = this.settingsManager.getLastChangelogVersion();
const changelogPath = getChangelogPath();
const entries = parseChangelog(changelogPath);
const selection = selectStartupChangelogEntries(entries, lastVersion);

if (!lastVersion) {
// Fresh install - record the version, send telemetry, don't show changelog
this.settingsManager.setLastChangelogVersion(VERSION);
if (selection.checkpointVersion && selection.checkpointVersion !== lastVersion) {
this.settingsManager.setLastChangelogVersion(selection.checkpointVersion);
this.reportInstallTelemetry(VERSION);
return undefined;
}

const newEntries = getNewEntries(entries, lastVersion);
if (newEntries.length > 0) {
this.settingsManager.setLastChangelogVersion(VERSION);
this.reportInstallTelemetry(VERSION);
return newEntries.map((e) => normalizeChangelogLinks(e.content, e)).join("\n\n");
}

return undefined;
return selection.entries.length > 0
? selection.entries.map((entry) => normalizeChangelogLinks(entry.content, entry)).join("\n\n")
: undefined;
}

private reportInstallTelemetry(version: string): void {
Expand Down
34 changes: 34 additions & 0 deletions packages/coding-agent/src/utils/changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,44 @@ export interface StartupChangelogPreviewOptions {
maxWidth?: number;
}

export interface StartupChangelogSelection {
checkpointVersion?: string;
entries: ChangelogEntry[];
}

function entryVersion(entry: ChangelogEntry): string {
return `${entry.major}.${entry.minor}.${entry.patch}`;
}

/**
* Select changelog entries that appear before the last acknowledged entry.
*
* Startup tracking follows document order instead of semver so repositories
* can reset their package version without replaying inherited release notes.
* Unknown markers are treated as a migration baseline rather than as a reason
* to display the complete changelog.
*/
export function selectStartupChangelogEntries(
entries: ChangelogEntry[],
lastVersion: string | undefined,
): StartupChangelogSelection {
const checkpointVersion = entries[0] ? entryVersion(entries[0]) : undefined;
if (!checkpointVersion || !lastVersion) {
return { checkpointVersion, entries: [] };
}

const normalizedLastVersion = lastVersion.trim().replace(/^v/i, "");
const lastSeenIndex = entries.findIndex((entry) => entryVersion(entry) === normalizedLastVersion);
if (lastSeenIndex <= 0) {
return { checkpointVersion, entries: [] };
}

return {
checkpointVersion,
entries: entries.slice(0, lastSeenIndex),
};
}

function normalizeTag(version: string | ChangelogEntry): string {
const versionString = typeof version === "string" ? version : entryVersion(version);
return versionString.startsWith("v") ? versionString : `v${versionString}`;
Expand Down
38 changes: 38 additions & 0 deletions packages/coding-agent/test/changelog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type ChangelogEntry,
formatStartupChangelogPreviewLines,
normalizeChangelogLinks,
selectStartupChangelogEntries,
} from "../src/utils/changelog.ts";

const entry: ChangelogEntry = {
Expand Down Expand Up @@ -80,3 +81,40 @@ describe("formatStartupChangelogPreviewLines", () => {
]);
});
});

describe("selectStartupChangelogEntries", () => {
const changelogEntries: ChangelogEntry[] = [
{ major: 0, minor: 3, patch: 0, content: "## [0.3.0]\n\n- Current Diffex release" },
{ major: 0, minor: 84, patch: 0, content: "## [0.84.0]\n\n- Inherited release" },
{ major: 0, minor: 83, patch: 0, content: "## [0.83.0]\n\n- Older inherited release" },
];

test("uses changelog order when package versions have been reset", () => {
expect(selectStartupChangelogEntries(changelogEntries, "0.84.0")).toEqual({
checkpointVersion: "0.3.0",
entries: [changelogEntries[0]],
});
});

test("acknowledges fresh installs and legacy markers without replaying history", () => {
expect(selectStartupChangelogEntries(changelogEntries, undefined)).toEqual({
checkpointVersion: "0.3.0",
entries: [],
});
expect(selectStartupChangelogEntries(changelogEntries, "0.2.0")).toEqual({
checkpointVersion: "0.3.0",
entries: [],
});
});

test("returns no startup entries after the current checkpoint is seen", () => {
expect(selectStartupChangelogEntries(changelogEntries, "v0.3.0")).toEqual({
checkpointVersion: "0.3.0",
entries: [],
});
expect(selectStartupChangelogEntries([], "0.3.0")).toEqual({
checkpointVersion: undefined,
entries: [],
});
});
});
Loading