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