From d41c0d172f3db072bc705c565c99bcb4600db559 Mon Sep 17 00:00:00 2001 From: "ki.sagidullin" Date: Mon, 17 Aug 2026 15:58:14 +0500 Subject: [PATCH] feat(app): add semantic Version constant 0.1.0 Add exported constant app.Version in major.minor.patch format (0.1.0) with documentation of the manual increment process (when to bump patch/minor/major). Include the version in the /help command reply and add tests verifying the semver format and the version presence in help text. --- internal/app/app.go | 54 +++++++++++++++++++++++++++++----------- internal/app/app_test.go | 21 +++++++++++++--- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index d7e49e9..537db76 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -30,6 +30,27 @@ import ( // создателем токена read:package (kamelion). Не требует конфигурации. const packageOwner = "kamelion" +// Version — семантический номер версии приложения в формате major.minor.patch +// (например "0.1.0"). Меняется вручную при выпуске новых изменений; НЕ должен +// вшиваться ldflag'ом или генерироваться автоматически. +// +// Правила ручного инкремента (когда и какую часть номера увеличивать): +// - patch (0.1.0 → 0.1.1): исправление багов и мелкие правки, новая +// функциональность не добавляется (обратно-совместимые изменения). +// - minor (0.1.0 → 0.2.0): появляется новая (обратно-совместимая) +// функциональность; patch при этом сбрасывается в 0. +// - major (0.1.0 → 1.0.0): несовместимые изменения API/поведения или крупные +// релизы; minor и patch сбрасываются в 0. +// +// Пока продукт не стабилен, major держим на 0 → версии идут 0.x.y +// (минорные правки с повышением minor, исправления — с повышением patch). +// +// ВАЖНО: это СЕМАНТИЧЕСКАЯ версия приложения (для людей и диагностики), её +// не следует путать с build-идентификатором `main.version` (commit-), +// который вшивается ldflag'ом и используется автообновлением. Здесь номер +// поднимается вручную перед каждым релизом/публикацией новой сборки. +const Version = "0.1.0" + // App — собранный конвейер. type App struct { Config *config.Config @@ -391,23 +412,26 @@ func (a *App) handleBinaryCommand(ctx context.Context, uid chat.UserID, text str } } -// helpText — текст команды /help. -const helpText = "⚡ **Ratatoskr** — помогу продумать задачу и запущу агента.\n\n" + - "**Команды:**\n" + - "`/start` — новая задача\n" + - "`/cancel` — отменить текущую\n" + - "`/skip` — хватит вопросов, предложить черновик\n" + - "`/retry N` — перезапустить задачу `N`\n" + - "`/status N` — статус задачи `N`\n" + - "`/continue N` — продолжить задачу `N`\n" + - "`/status` — статус бинаря и обновления\n" + - "`/update` — применить обновление\n" + - "`/help` — эта справка\n\n" + - "Просто опишите задачу — я помогу её продумать." +// helpTextFor — полный текст команды /help с номером версии приложения. +func helpTextFor(version string) string { + return "⚡ **Ratatoskr** — помогу продумать задачу и запущу агента.\n\n" + + "**Команды:**\n" + + "`/start` — новая задача\n" + + "`/cancel` — отменить текущую\n" + + "`/skip` — хватит вопросов, предложить черновик\n" + + "`/retry N` — перезапустить задачу `N`\n" + + "`/status N` — статус задачи `N`\n" + + "`/continue N` — продолжить задачу `N`\n" + + "`/status` — статус бинаря и обновления\n" + + "`/update` — применить обновление\n" + + "`/help` — эта справка\n\n" + + "Версия: " + version + "\n\n" + + "Просто опишите задачу — я помогу её продумать." +} -// handleHelpCmd отвечает списком команд. +// handleHelpCmd отвечает списком команд и номером версии приложения. func (a *App) handleHelpCmd(ctx context.Context, uid chat.UserID) { - a.send(ctx, uid, helpText) + a.send(ctx, uid, helpTextFor(Version)) } // handleStatusCmd отвечает текущей версией и результатом последней проверки. diff --git a/internal/app/app_test.go b/internal/app/app_test.go index cf7ba90..900ef8b 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -4,6 +4,7 @@ import ( "context" "os" "path/filepath" + "regexp" "strings" "testing" ) @@ -216,17 +217,31 @@ func TestHelp_IsBinaryCommand(t *testing.T) { } } -// TestHelpText_ContainsCommands проверяет, что справка упоминает все команды. +// TestHelpText_ContainsCommands проверяет, что справка упоминает все команды +// и номер версии приложения. func TestHelpText_ContainsCommands(t *testing.T) { + text := helpTextFor(Version) for _, cmd := range []string{ "/start", "/cancel", "/skip", "/retry", "/status", "/continue", "/update", "/help", } { - if !strings.Contains(helpText, cmd) { + if !strings.Contains(text, cmd) { t.Errorf("helpText не упоминает команду %s", cmd) } } - if !strings.Contains(helpText, "Ratatoskr") { + if !strings.Contains(text, "Ratatoskr") { t.Errorf("helpText не содержит имени бота") } + if !strings.Contains(text, Version) { + t.Errorf("helpText не содержит номера версии приложения %s", Version) + } +} + +// TestVersion_Semver проверяет, что константа версии приложения объявлена +// в формате major.minor.patch (например 0.1.0). +func TestVersion_Semver(t *testing.T) { + re := regexp.MustCompile(`^\d+\.\d+\.\d+$`) + if !re.MatchString(Version) { + t.Errorf("Version = %q, ожидался формат major.minor.patch", Version) + } } \ No newline at end of file