Merge pull request 'feat/200acbbe17dab7c1' (#1) from feat/200acbbe17dab7c1 into main
All checks were successful
CI / test (push) Successful in 45s
CI / build-and-package (amd64, linux) (push) Successful in 44s
CI / build-and-package (amd64, windows) (push) Successful in 42s

Reviewed-on: http://gitea.hal9000.home/kamelion/ratatoskr-go/pulls/1
This commit was merged in pull request #1.
This commit is contained in:
2026-08-17 16:05:54 +05:00
3 changed files with 60 additions and 19 deletions

View File

@@ -52,7 +52,9 @@ func main() {
} }
} }
log.Print("ratatoskr: запущен") // Строка с номером версии при старте: семантическая версия (app.Version)
// + build-идентификатор (main.version, вшитый ldflag'ом) для диагностики.
log.Printf("ratatoskr: запущен (version %s, build %s)", app.Version, version)
if err := a.Run(context.Background()); err != nil { if err := a.Run(context.Background()); err != nil {
log.Fatalf("app run: %v", err) log.Fatalf("app run: %v", err)
} }

View File

@@ -30,6 +30,27 @@ import (
// создателем токена read:package (kamelion). Не требует конфигурации. // создателем токена read:package (kamelion). Не требует конфигурации.
const packageOwner = "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-<sha7>),
// который вшивается ldflag'ом и используется автообновлением. Здесь номер
// поднимается вручную перед каждым релизом/публикацией новой сборки.
const Version = "0.1.0"
// App — собранный конвейер. // App — собранный конвейер.
type App struct { type App struct {
Config *config.Config Config *config.Config
@@ -391,23 +412,26 @@ func (a *App) handleBinaryCommand(ctx context.Context, uid chat.UserID, text str
} }
} }
// helpText текст команды /help. // helpTextFor — полный текст команды /help с номером версии приложения.
const helpText = "⚡ **Ratatoskr** — помогу продумать задачу и запущу агента.\n\n" + func helpTextFor(version string) string {
"**Команды:**\n" + return "⚡ **Ratatoskr** — помогу продумать задачу и запущу агента.\n\n" +
"`/start` — новая задача\n" + "**Команды:**\n" +
"`/cancel` — отменить текущую\n" + "`/start` — новая задача\n" +
"`/skip` — хватит вопросов, предложить черновик\n" + "`/cancel` — отменить текущую\n" +
"`/retry N` — перезапустить задачу `N`\n" + "`/skip` — хватит вопросов, предложить черновик\n" +
"`/status N` — статус задачи `N`\n" + "`/retry N` — перезапустить задачу `N`\n" +
"`/continue N` — продолжить задачу `N`\n" + "`/status N` — статус задачи `N`\n" +
"`/status` — статус бинаря и обновления\n" + "`/continue N` — продолжить задачу `N`\n" +
"`/update` — применить обновление\n" + "`/status` — статус бинаря и обновления\n" +
"`/help` — эта справка\n\n" + "`/update` — применить обновление\n" +
"Просто опишите задачу — я помогу её продумать." "`/help` — эта справка\n\n" +
"Версия: " + version + "\n\n" +
"Просто опишите задачу — я помогу её продумать."
}
// handleHelpCmd отвечает списком команд. // handleHelpCmd отвечает списком команд и номером версии приложения.
func (a *App) handleHelpCmd(ctx context.Context, uid chat.UserID) { func (a *App) handleHelpCmd(ctx context.Context, uid chat.UserID) {
a.send(ctx, uid, helpText) a.send(ctx, uid, helpTextFor(Version))
} }
// handleStatusCmd отвечает текущей версией и результатом последней проверки. // handleStatusCmd отвечает текущей версией и результатом последней проверки.

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"testing" "testing"
) )
@@ -216,17 +217,31 @@ func TestHelp_IsBinaryCommand(t *testing.T) {
} }
} }
// TestHelpText_ContainsCommands проверяет, что справка упоминает все команды. // TestHelpText_ContainsCommands проверяет, что справка упоминает все команды
// и номер версии приложения.
func TestHelpText_ContainsCommands(t *testing.T) { func TestHelpText_ContainsCommands(t *testing.T) {
text := helpTextFor(Version)
for _, cmd := range []string{ for _, cmd := range []string{
"/start", "/cancel", "/skip", "/retry", "/status", "/continue", "/start", "/cancel", "/skip", "/retry", "/status", "/continue",
"/update", "/help", "/update", "/help",
} { } {
if !strings.Contains(helpText, cmd) { if !strings.Contains(text, cmd) {
t.Errorf("helpText не упоминает команду %s", cmd) t.Errorf("helpText не упоминает команду %s", cmd)
} }
} }
if !strings.Contains(helpText, "Ratatoskr") { if !strings.Contains(text, "Ratatoskr") {
t.Errorf("helpText не содержит имени бота") 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)
}
} }