commit 53cf901707b6aa601a6edcdb4c3c59ac8e5e69b0 Author: Hermes Date: Fri Aug 14 08:53:57 2026 +0500 ratatoskr-go: стартовый каркас (Go-бинарь, CI, structure) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..7ccc5e3 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,16 @@ +name: build-test +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + - run: go vet ./... + - run: go build ./... + - run: go test ./... \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..caf5f4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Binaries +/bin/ +*.exe +*.out + +# Go build/test artifacts +/testbin/ +*.test + +# Env / secrets +.env +.env.* +!/.env.example +secrets/ + +# Local scratch +.tmp/ +.db/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..05ad0fb --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Ratatoskr-go + +Ratatoskr на Go — **оркестратор в единый статический бинарь** + субагенты через +[opencode](https://opencode.ai) как внешний процесс. Переезд с Python-версии. + +## Почему Go + +Агентный конвейер — это клей (HTTP/JSON к LLM, SQLite, Gitea API, Telegram), а не +горячие вычисления. Цель — **один статический бинарь без runtime-зависимостей** +(CGO_ENABLED=0), который решает проблему доставки/деплоя. Go: горизонт +HTTP/JSON + goroutine-параллелизм research/feedback/notify + SQLite без CGO +(`modernc.org/sqlite`). + +## Структура + +``` +cmd/ratatoskr/ # точка входа, сборка бинаря +internal/ + app/ # компоновка (composition root), конфиг, DI + dialog/ # state-machine диалога: collect→confirm→created + decide/ # аналитик: промпт + разбор JSON-решения (opencode agent) + research/ # исследователь: FACTS о репозитории + feedback/ # feedback: транскрипт→proposals (opencode agent) + opencode/ # обёртка запуска opencode-процесса, парсинг вердикта + gitea/ # Gitea API-клиент: issues, labels, PR, webhook + webhook/ # digest-ответы и статус в Telegram + issue/ # issue: формирование тела задачи, task-tag +``` + +## Сборка + +```bash +go build -o bin/ratatoskr ./cmd/ratatoskr # обычная +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -o bin/ratatoskr-linux-amd64 ./cmd/ratatoskr # статический бинарь +``` + +## Контракты (не ломать) + +- `process_turn` всегда возвращает **3 значения**: `(reply, action, session)`. +- `decide_fn` возвращает кортеж/структуру `(decision, err)`. +- `/proposal` передаёт аналитику **полный комплект**, а не digest. +- Вердикты агентов — строгий JSON; `_extract_json` устойчив к мусору. + +## Eval + +Eval-suite (парсинг A / контракты B / качество C) — см. +[дизайн в вики](../wiki/concepts/ratatoskr-evals.md). Запуск после внедрения: +`go test ./...` для детерминированных уровней A+B. \ No newline at end of file diff --git a/cmd/ratatoskr/main.go b/cmd/ratatoskr/main.go new file mode 100644 index 0000000..26a3e18 --- /dev/null +++ b/cmd/ratatoskr/main.go @@ -0,0 +1,44 @@ +// Command ratatoskr — оркестратор конвейера Ratatoskr (единственный бинарь). +// +// Стартовый каркас: точка входа + заглушки пакетов. Полная логика +// (диалог, аналитик, research, feedback, webhook) наполняется по мере +// переезда с Python. +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/kamelion/ratatoskr-go/internal/app" +) + +func main() { + cfg := flag.String("config", "", "путь к config файлу (по умолчанию — env)") + version := flag.Bool("version", false, "показать версию и выйти") + flag.Parse() + + if *version { + fmt.Println("ratatoskr-go dev (переезд с Ratatoskr Python)") + return + } + + // Composition root: читает конфиг (env/config), настраивает DI. + a, err := app.New(*cfg) + if err != nil { + log.Fatalf("app init: %v", err) + } + + // Пока: заглушка. Здесь будет: + // - запуск Telegram-gateway (диалог) + // - http-сервер webhook (+ /feedback, /status) + // - воркер: обработка issues → opencode-субагенты → PR + _ = a + os.Exit(run()) +} + +func run() int { + fmt.Fprintln(os.Stderr, "ratatoskr-go: каркас готов, конвейер не подключён") + return 0 +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..667a7bf --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/kamelion/ratatoskr-go + +go 1.23 \ No newline at end of file diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..4734758 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,26 @@ +// Package app — composition root: конфигурация, DI, жизненный цикл процесса. +package app + +// Config — конфигурация конвейера (env или config-файл). +type Config struct { + GiteaURL string + GiteaToken string + IssueRepo string + // TODO: Telegram, LiteLLM, SQLite (opencode.db), gateway, webhook secret. +} + +// App — собранный конвейер (все зависимости). +type App struct { + Config Config + // TODO: *dialog.Controller, *gitea.Client, *opencode.Runner, ... +} + +// New читает конфиг и собирает приложение. Стартовая заглушка. +func New(configPath string) (*App, error) { + var c Config + _ = configPath // TODO: загрузка из env/config + // TODO: слой failures/classes ошибок по конвенции проекта. + // Использовать в рантайме (например, лимиты диалога): + c.IssueRepo = "kamelion/ratatoskr-go" // placeholder + return &App{Config: c}, nil +} \ No newline at end of file diff --git a/internal/app/app_test.go b/internal/app/app_test.go new file mode 100644 index 0000000..459e4c0 --- /dev/null +++ b/internal/app/app_test.go @@ -0,0 +1,15 @@ +package app + +import "testing" + +// Контракт: process_turn (после внедрения) должен возвращать 3 значения. +// Здесь — placeholder-проверка, что composition root собирается без сбоев. +func TestNew(t *testing.T) { + a, err := New("") + if err != nil { + t.Fatalf("New() err = %v", err) + } + if a.Config.IssueRepo == "" { + t.Fatal("IssueRepo пуст — каркас не инициализирован") + } +} \ No newline at end of file