Files
ratatoskr-go/README.md
Hermes 46065f84fd
Some checks failed
CI / test (push) Failing after 52s
CI / build-and-package (amd64, linux) (push) Failing after 27s
CI / build-and-package (amd64, windows) (push) Failing after 28s
feat: фаза ready — черновик готов как есть, без требований к изменённым полям
Аналитик теперь может вернуть phase=ready, когда черновик уже полный и
менять нечего. Раньше модель не могла это выразить и возвращала пустой
propose, который валидатор резал A3 (нет изменённых полей).

- analyst: case ready в validateResponse (без требований к полям)
- core: propose и ready обрабатываются одинаково (applyDraft + E1 + ready)
- prompt/analyst.md: контракт фаз обновлён (ask|propose|ready|abort)
- тест TestDecideReady + README: раздел фаз аналитика
2026-08-17 14:07:05 +05:00

222 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ratatoskr-go
Ratatoskr на Go — **оркестратор в единый статический бинарь** + субагенты через
[opencode](https://opencode.ai) как внешний процесс. Переезд с Python-версии:
все подсистемы (диалог, воркер, автообновление) собраны в один исполняемый
файл без runtime-зависимостей (`CGO_ENABLED=0`), что упрощает доставку и деплой.
## Структура
```
cmd/ratatoskr/ # точка входа, сборка бинаря (App.New + Run)
internal/
app/ # компоновка (composition root), config, DI, packageOwner
config/ # загрузка YAML+env: ε подстановка ${VAR:-default}, defaults, validate (классы C1C4)
chat/ # мультиканальный Router; telegram — long-poll канал
core/ # state-machine задач: /start /cancel /skip /retry N /status N /continue N
analyst/ # аналитик: промпт + разбор JSON-решения (opencode agent, классы A1A4)
worker/ # polling-планировщик + dev/reviewer-конвейер (классы W*, E*, R*)
agents/ # встроенные агенты opencode (analyst.md, dev.md, reviewer.md) через go:embed
opencode/ # обёртка запуска opencode-процесса, парсинг вердикта (классы O1O4)
storage/ # SQLite (modernc.org/sqlite, без CGO): tasks, traces, task_history (S1S5)
update/ # автообновление из Gitea Packages (классы U1U6)
```
## Сборка
Требуется Go (на хосте без `go` в PATH — экспорт вручную):
```bash
export PATH=/opt/data/.local/go/bin:$PATH # локальный Go-тулчейн
go version # go1.25.0 linux/amd64
```
### Обычная сборка (текущая платформа)
```bash
cd /opt/data/src/ratatoskr-go
make build # go build -ldflags="-s -w" -o ratatoskr ./cmd/ratatoskr/
./ratatoskr -version
```
### Кросс-сборка под linux/amd64 и windows/amd64 (только эти две платформы)
```bash
make cross
# → ratatoskr-linux-amd64
# → ratatoskr-windows-amd64.exe (Windows-машина Камиля)
```
`Makefile` вшивает в бинарь версию (ldflag):
```make
GIT_SHA := $(shell git rev-parse --short HEAD || echo dev)
GOFLAGS ?= -ldflags="-s -w -X main.version=commit-$(GIT_SHA) -X main.updateToken=$(UPDATE_TOKEN)"
```
- `main.version` = `commit-<sha7>` — та же версия, под которой бинарь публикуется в Gitea Packages.
- `main.updateToken` = токен read:package для автообновления (см. ниже). Если не задан
(`UPDATE_TOKEN ?= ""`) — пустая встройка, тогда обновление берёт `update.token` из конфига.
**Как на самом деле публикуется рабочий бинарь:** локальный `go build` токен НЕ вшивает
(значение секрета есть только у CI). Запушь `main` — CI (см. ниже) сам соберёт оба бинаря
со вшитым `TC_UPDATE_TOKEN` и загрузит их в Gitea Packages. Так соблюдён least-privilege:
read-токен не светится на хосте разработки.
### Проверки перед сборкой
```bash
make test # go test ./... -v -count=1 -timeout 120s
make vet # go vet ./...
```
### Прогон
```bash
make run # build + ./ratatoskr -config config.yaml
```
## CI (.gitea/workflows/ci.yaml)
- **Job `test`**: checkout → setup-go → `go test ./...``go vet ./...`.
- **Job `build-and-package`** (matrix linux/amd64 + windows/amd64, без артефактов):
собирает бинарь со вшитым `-X main.updateToken=${secrets.TC_UPDATE_TOKEN}` и
**публикует в Gitea Packages** — только на `main`, сразу в версию `commit-<sha7>/`
(псевдо-версия `latest` НЕ используется — см. автообновление). Плюс companion-файлы
`<file>.version` и `<file>.sha256`.
Секреты/vars: `TC_GITEA_TOKEN` (write:packages), `TC_UPDATE_TOKEN` (read:package),
`GIT_MAIN_URL`.
## Конфигурация (config.yaml)
Минимальный рабочий конфиг (порядок полей произвольный) и блок автообновления
(подробнее в `config.yaml.example`):
```yaml
telegram:
token: "..." # TG_TOKEN
chat_id: "..." # TG_CHAT_ID
opencode:
bin: "opencode"
config_dir: "./agents" # каталог, куда распаковываются встроенные агенты
paths:
db: "./ratatoskr.db" # дефолт; резолвится от каталога бинаря (не от cwd)
worktree: "./worktrees" # то же правило «всё рядом с .exe»
update:
enabled: true
base_url: "http://gitea.hal9000.home" # env UPDATE_BASE_URL; пуст = обновление ОТКЛЮЧЕНО
token: "${UPDATE_TOKEN}" # env UPDATE_TOKEN — фоллбэк, если не вшит ldflag
package: "ratatoskr"
check_interval: 24h
```
**Пути «всё рядом с .exe»:** относительные `db`/`worktree` резолвятся к каталогу
бинаря (`config.ExeDir()`), а НЕ к cwd. `config.yaml` ищется сначала рядом с бинарём,
фоллбэк — cwd. Абсолютные пути не трогаются.
## Команды бота
| Команда | Описание |
|---|---|
| `/start` | начать новую задачу |
| `/cancel` | отменить активную задачу |
| `/skip` | пропустить шаг |
| `/retry N` | повторить задачу N |
| `/status N` | статус задачи N |
| `/continue N` | продолжить |
| `/update` | проверить + применить обновление из Gitea Packages |
| `/status` | версия бинаря + есть ли доступное обновление |
| `/help` | справка по всем командам |
## Фазы аналитика
Аналитик (`internal/analyst`) возвращает JSON-вердикт с полем `phase`:
| phase | Смысл | Требования валидатора |
|---|---|---|
| `ask` | данных не хватает — задаёт уточняющие вопросы | есть `chat_reply` или `questions` |
| `propose` | черновик достаточно заполнен, вносятся правки | хотя бы одно изменённое поле (`title`/`goal`/`repos`/`why`/`ac`) |
| `ready` | черновик уже полный и готов как есть — менять нечего | изменённых полей не требуется |
| `abort` | тема не про код / не подходит | опционально `abort_reason` |
`ready` был добавлен как явная фаза для случая, когда аналитик видит полностью готовый
черновик и просто запускает задачу агенту-кодеру (раньше модель не могла это выразить и
возвращала пустой `propose`, который валидатор резал как «нет изменённых полей»).
В `internal/core` фазы `propose` и `ready` обрабатываются одинаково (применить черновик,
проверить репозитории, поставить `ready` и отдать резюме).
## Автообновление из Gitea Packages
Бинарь умеет сам себя обновлять из generic-пакета в Gitea. Модель:
**авто = только Check + уведомление** владельцу («доступна версия X, выполни /update»),
**применение — только по `/update`**.
- **Источник версий:** `{update.base_url}/api/packages/{owner}/generic/{package}`,
где `owner = "kamelion"` (константа `packageOwner` в коде), `package = "ratatoskr"`.
- **Каждая публикация — в конкретную версию `commit-<sha7>/`** (никакого `latest/`:
при параллельных PUT матрицы CI псевдо-версия `latest` разъезжается, и контрольные
суммы не сходятся — класс ошибки U4). Новейшая применимая версия ищется листингом:
фильтр `commit-*`, проба скачать бинарь платформы, берём версию с макс `id`.
- **Companion-файлы** рядом с бинарём в той же версии: `.<file>.version` (id версии)
и `<file>.sha256` (контрольная сумма). Verify всегда сверяет все три файла **одной** версии.
- **Update-хост ОТЛЕН от git-хоста:** `update.base_url` свой (env `UPDATE_BASE_URL`),
фоллбэка на `git.base_url` НЕТ. Пустой `update.base_url` полностью отключает обновление.
- **Токен доступа — только `read:package`, НЕ `git.token`** (least-privilege: update ≠ git).
Приоритет: вшитый ldflag `-X main.updateToken` (секрет CI `TC_UPDATE_TOKEN`) >
`update.token` из конфига (`UPDATE_TOKEN`). Заголовок: `Authorization: token <токен>`.
- **Применение (свап):** POSIX — exec-перезапуск процесса; Windows — мгновенный exec
невозможен (файл занят), поэтому `.new` применяется при следующем старте
(`ApplyPendingSwap` в main.go до подсистем). Резервный `.old` для отката.
### Как опубликовать новую версию
1. Внеси правки, коммитни, запуши в `main`.
2. CI соберёт оба бинаря со вшитым `TC_UPDATE_TOKEN` и опубликует в
`.../generic/ratatoskr/commit-<sha7>/` (бинарь + `.version` + `.sha256`).
3. На обновляемой машине: бот сам уведомит о новой версии → `/update`.
Проверку автообновления после публикации гоняет владелец на своей
Windows-машине (не прогоняется агентом).
### Валидация опубликованной версии
```bash
B=http://gitea.hal9000.home
V=commit-<sha7>; f=ratatoskr-linux-amd64
curl -s -H "Authorization: token $TOKEN" -o /tmp/probe.bin "$B/api/packages/kamelion/generic/ratatoskr/$V/$f"
echo "actual: $(sha256sum /tmp/probe.bin)" # реальная сумма скачанного
curl -s -H "Authorization: token $TOKEN" "$B/api/packages/kamelion/generic/ratatoskr/$V/$f.version"
curl -s -H "Authorization: token $TOKEN" "$B/api/packages/kamelion/generic/ratatoskr/$V/$f.sha256"
# .version == $V и .sha256 == actual → версия целостна
```
## Классы ошибок
| Блок | Коды | Где |
|---|---|---|
| C | C1C4 | `internal/config` |
| A | A1A4 | `internal/analyst` |
| M | M1M5 | `internal/chat` |
| O | O1O4 | `internal/opencode` |
| S | S1S5 | `internal/storage` |
| W | W1W5 | `internal/worker` |
| E | E1E4 | `internal/worker` (репозитории) |
| R | R1R6 | `internal/worker/review_errors.go` (ревьюер) |
| U | U1U6 | `internal/update` |
## Контракты (не ломать)
- `App.New(configPath, version, updateToken string)` — сигнатура: при правках обновлять
все вызовы в `internal/app/app_test.go` (иначе `go vet` падает).
- `packageOwner` — константа (`"kamelion"`) в `internal/app/app.go`: владелец пакета ==
владелец репо == создатель токена; инвариант на проект, НЕ плодить vars/ldflag/конфиг.
- `update.Updater` создаётся структурой `&update.Updater{...}` напрямую (конструктора
`NewUpdater` нет).
- `process_turn` (Python-версия) всегда возвращает 3 значения; `decide_fn` — кортеж
`(decision, err)`. Вердикты агентов — строгий JSON.
## Дополнительно
- Дизайн eval-suite (парсинг A / контракты B / качество C) — `../wiki/concepts/ratatoskr-evals.md`.
- Прочие команды/go-модель — см. скилл `ratatoskr-development`.