Журнал изменений — часть продукта, а не git log

Product

Журналы изменений бывают двух видов.

Первый собирают из сообщений коммитов. В нём строки вроде fix: handle null in resolver и chore: bump deps. Формально это полный список изменений, и ни один пользователь не прочитал его дважды.

Второй пишут для человека, который пользуется вашим продуктом. В нём написано, что теперь можно сделать такого, чего на прошлой неделе было нельзя. Он короткий. На него подписываются.

Разница не в инструментах, а в том, что кто-то решил считать журнал изменений частью продукта.

Дайте каждой записи тип

Больше всего даёт одно изменение: у каждой записи появляется тип. В Kookee их семь:

feature · fix · improvement · breaking · security · deprecated · other

Это не украшение. Благодаря типам журнал можно фильтровать, а значит, он полезен не одной аудитории, а нескольким:

TypeScript
// Что нового — для пользователей
const features = await kookee.changelog.list({
  filter: { changelogType: 'feature' },
  limit: 10,
});

// Что сломается — для интеграторов
const breaking = await kookee.changelog.list({ filter: { changelogType: 'breaking' } });

Инженеру, который подключает ваш API, важны breaking и deprecated, и больше ничего. Пользователю важен feature. Команда безопасности хочет видеть security на отдельной странице. Один список с типами подходит всем троим, а плоский список толком не подходит никому.

Тип — это структурированное поле, а не свойство самой записи, и при выводе это важно. entry.type — это строка 'changelog', а тип релиза достают из массива fields:

TypeScript
import { fieldOptionKey, fieldStringValue } from '@kookee/sdk';

const releaseType = fieldOptionKey(entry.fields, 'changelogType'); // 'feature'
const version = fieldStringValue(entry.fields, 'version');         // '1.6.0'

Дайте каждой записи версию

Тип отвечает на вопрос «что это за изменение». Версия отвечает на вопрос «есть ли это уже у меня», и именно его задаёт пользователь, который пришёл из переписки с поддержкой.

Версия — свободный текст, поэтому подойдёт любая ваша схема нумерации: 1.6.0, 2024.11, v3-beta. Чтобы от версий была польза, нужны две привычки: ставить версию у каждой записи и держать одну схему для всех. Журнал, где версия есть только у половины записей, выглядит как недосмотр, и это он и есть.

Когда спрашивают, что вошло в конкретный релиз, фильтруйте по точной версии:

TypeScript
const release = await kookee.changelog.list({ filter: { version: '1.6.0' } });

Записи приходят от новых к старым. Для страницы релизов вам, скорее всего, нужен строгий порядок по версиям: даты врут, когда патч бэкпортировали или релиз задержался. Отсортируйте записи сами, потому что при сортировке версий как строк 1.10.0 оказывается раньше 1.9.0:

TypeScript
const { data } = await kookee.changelog.list({ limit: 100 });

const byVersion = [...data].sort((a, b) =>
  (fieldStringValue(b.fields, 'version') ?? '').localeCompare(
    fieldStringValue(a.fields, 'version') ?? '',
    undefined,
    { numeric: true },
  ),
);

Именно numeric: true ставит 1.10.0 после 1.9.0.

Дайте людям реагировать

На каждую запись журнала можно ответить эмодзи-реакцией:

TypeScript
await kookee.changelog.react(entryId, { reactionType: 'fire', action: 'add' });

С виду это мелкая социальная функция. На деле это самый дешёвый канал обратной связи о продукте, какой вы когда-нибудь сделаете. Вы выпустили десять изменений за месяц и видите, какие два собрали все реакции, — значит, знаете, на что потратить следующий месяц. А пользователю это стоит одного клика, а не опроса.

Пишите запись вместе с релизом

На практике беда не в плохих записях, а в том, что их пишут через неделю, по памяти и пачкой. К этому времени никто уже не помнит, чем изменение было важно, и сам собой получается журнал из сообщений коммитов.

Запись в Kookee готовят черновиком, пока пулл-реквест ещё открыт, и публикуют, когда выходит релиз: без деплоя, без пересборки, без ожидания генератора статических сайтов. Пишите черновики в панели управления или через ИИ-ассистента для кода, подключённого по MCP. Он прочитает несколько последних записей и напишет следующую в том же тоне, с типом и версией, а вы её проверите.

Потом запись доступна одним запросом, где бы она ни понадобилась:

TypeScript
const { data } = await kookee.changelog.list({ limit: 5 });

Ваш маркетинговый сайт, панель «Что нового» в приложении, письмо о релизе. Один источник, написанный один раз человеком, который и сделал изменение.