3 Commits

Author SHA1 Message Date
ki.sagidullin
5c82229a00 chore(serena): обновить память проекта (onboarding)
Some checks failed
CI / test (pull_request) Failing after 1m13s
CI / build-and-package (amd64, linux) (pull_request) Failing after 1m2s
CI / build-and-package (amd64, windows) (pull_request) Successful in 28s
2026-08-21 00:08:45 +05:00
ki.sagidullin
612446d65b chore(scripts): add build-publish-ui.ps1 2026-08-21 00:05:54 +05:00
60a6966a95 Merge pull request 'feat/new_ui' (#8) from feat/new_ui into main
Some checks failed
CI / test (push) Failing after 1m8s
CI / build-and-package (amd64, linux) (push) Failing after 58s
CI / build-and-package (amd64, windows) (push) Successful in 28s
Reviewed-on: http://gitea.hal9000.home/kamelion/ratatoskr-go/pulls/8
2026-08-20 14:50:07 +05:00
10 changed files with 107 additions and 175 deletions

View File

@@ -14,12 +14,6 @@ concurrency:
group: ${{ gitea.workflow }}-${{ gitea.ref }} group: ${{ gitea.workflow }}-${{ gitea.ref }}
cancel-in-progress: true cancel-in-progress: true
# CI собирает/тестирует ТОЛЬКО headless-вариант: приложение без графического окна
# (internal/app/ui_noui.go). UI (Fyne-окно) требует cgo и C-компилятора, поэтому
# собирается локально и публикуется скриптом scripts/build-publish-ui.ps1.
env:
CGO_ENABLED: '0'
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -28,11 +22,11 @@ jobs:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '1.25' go-version: '1.23'
check-latest: true check-latest: true
- name: Run tests - name: Run tests
run: go test ./... -v -count=1 -timeout 300s 2>&1 run: go test ./... -v -count=1 -timeout 120s 2>&1
- name: Vet - name: Vet
run: go vet ./... run: go vet ./...
@@ -40,22 +34,27 @@ jobs:
build-and-package: build-and-package:
if: always() if: always()
needs: test needs: test
# Только linux: headless-бинарь для сервера. Windows-бинарь (с UI, cgo) strategy:
# публикуется локально через scripts/build-publish-ui.ps1. matrix:
goos: [linux, windows]
goarch: [amd64]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '1.25' go-version: '1.23'
check-latest: true check-latest: true
- name: Build binary - name: Build binary
run: | run: |
FILENAME="ratatoskr-linux-amd64" FILENAME="ratatoskr-${{ matrix.goos }}-${{ matrix.goarch }}"
if [ "${{ matrix.goos }}" = "windows" ]; then
FILENAME="${FILENAME}.exe"
fi
VERSION="commit-$(echo '${{ gitea.sha }}' | cut -c1-7)" VERSION="commit-$(echo '${{ gitea.sha }}' | cut -c1-7)"
GOOS=linux GOARCH=amd64 \ GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \
go build -ldflags="-s -w -X main.version=${VERSION} -X main.updateToken=${{ secrets.TC_UPDATE_TOKEN }}" -o "${FILENAME}" ./cmd/ratatoskr/ go build -ldflags="-s -w -X main.version=${VERSION} -X main.updateToken=${{ secrets.TC_UPDATE_TOKEN }}" -o "${FILENAME}" ./cmd/ratatoskr/
echo "FILENAME=${FILENAME}" >> "${GITEA_ENV}" echo "FILENAME=${FILENAME}" >> "${GITEA_ENV}"
echo "VERSION=${VERSION}" >> "${GITEA_ENV}" echo "VERSION=${VERSION}" >> "${GITEA_ENV}"

9
.gitignore vendored
View File

@@ -4,12 +4,6 @@
*.out *.out
ratatoskr ratatoskr
# Publish companions (scripts/build-publish-ui.ps1)
ratatoskr-*.exe.sha256
ratatoskr-*.exe.version
ratatoskr-*.sha256
ratatoskr-*.version
# Go build/test artifacts # Go build/test artifacts
/testbin/ /testbin/
*.test *.test
@@ -20,9 +14,6 @@ ratatoskr-*.version
!/.env.example !/.env.example
secrets/ secrets/
# Local Gitea credentials for scripts/build-publish-ui.ps1
scripts/.gitea-creds
# Local config (not tracked — use config.yaml.example) # Local config (not tracked — use config.yaml.example)
config.yaml config.yaml

View File

@@ -1,10 +1,12 @@
# conventions # conventions
## Стиль / кодстайл ## Стиль / кодстайл
- Стандартный Go-стиль; гофм `gofmt`/`go fmt ./...`. Документация-комментарии и - Стандартный Go-стиль; `gofmt`/`go fmt ./...`. Документация-комментарии и
package-doc на русском языке (в START-комментариях файлов и doc-комментариях). package-doc на русском языке.
- Типизация: строгие типы, интерфейсы для абстракций (Decider, LiveProber). - Типизация: строгие типы, интерфейсы для абстракций (Decider, ui.Store, events.Event, chat.Channel).
- Свой тип `config.Duration` для времени (YAML-строки "5s"/"10m"), метод `.Duration()`. - Свой тип `config.Duration` для времени (YAML-строки "5s"/"10m"), метод `.Duration()`.
- UI: uni-directional data flow — UI читает снапшоты (копии, `ui.Store`), не мутирует storage/core.
Ядро публикует события в `events.Bus`, UI подписывается `events.Hub` → колбэки в UI-поток через `fyne.Do`.
## Обработка ошибок ## Обработка ошибок
- Ошибки классифицируются по идентификаторам классов в исходниках (см. ниже). - Ошибки классифицируются по идентификаторам классов в исходниках (см. ниже).
@@ -15,9 +17,10 @@
| Блок | Коды | Где | | Блок | Коды | Где |
|---|---|---| |---|---|---|
| C | C1C4 | internal/config | | C | C1C4 | internal/config |
| A | A1A4 | internal/analyst | | A | A1A4 | internal/analyst; A (app): A1A4 в internal/app |
| M | M1M5 | internal/chat | | M | M1M5 | internal/chat |
| O | O1O4 | internal/opencode | | D | D1D5 | internal/core (ErrDecideFailed, ErrMaxTurns, ErrCommandUnknown…) |
| O | O1O5 | internal/opencode |
| S | S1S5 | internal/storage | | S | S1S5 | internal/storage |
| W | W1W5 | internal/worker | | W | W1W5 | internal/worker |
| E | E1E4 | internal/worker (репозитории) | | E | E1E4 | internal/worker (репозитории) |
@@ -25,15 +28,16 @@
| U | U1U6 | internal/update | | U | U1U6 | internal/update |
## Архитектурные конвенции ## Архитектурные конвенции
- **Composition root** — internal/app; подсистемы собираются там, лимиты Core - **Composition root** — internal/app; подсистемы собираются там. UI-окно создаётся через
(MaxTurns=15, MaxConfirmCycles=3, MaxQuestionsPerTurn=5) в core.New. `newUIWindow(store)` (build-tag: cgo → desktop.New, !cgo → nil/headless). Флаг `--noui` выключает окно.
- **Агенты** (analyst.md/dev.md/reviewer.md) — markdown-промпты, встроены через go:embed - **Слои:** model (домен) ← storage/events/UI зависят только от model; core не знает про UI.
(internal/agents/*.md + embed.go), распаковываются в config_dir. Вердикт — строгий JSON. - **Агенты** (analyst.md/dev.md/reviewer.md) — markdown-промпты, go:embed, распаковка в config_dir.
Вердикт — строгий JSON.
- Репо-клонирование/воркеры — через gitops; ветки задач `feat/<taskTag>` (см. mem:core). - Репо-клонирование/воркеры — через gitops; ветки задач `feat/<taskTag>` (см. mem:core).
- Обратная совместимость: поле `Repo` (одиночный) и `Repos` (список); EffectiveRepos/ - Обратная совместимость: поле `Repo` (одиночный) и `Repos` (список); EffectiveRepos/
SetReposFromDB/ReposJoined в storage/models.go. SetReposFromDB/ReposJoined в storage/models.go.
## Версии ## Версии
- `app.Version` — семантическая major.minor.patch (ручной инкремент: patch=фиксы, - `app.Version` — семантическая major.minor.patch (ручной инкремент: patch=фиксы,
minor=новая обратно-совместимая функциональность, major=несовместимые изменения). minor=новая обратно-совместимая функциональность, major=несовместимые изменения). Сейчас 0.2.2.
- `main.version` (ldflag) — build-идентификатор `commit-<sha7>`, отдельно от app.Version. - `main.version` (ldflag) — build-идентификатор `commit-<sha7>`, отдельно от app.Version.

View File

@@ -1,70 +1,57 @@
# core # core
Ratatoskr-go — оркестратор конвейера Ratatoskr (порт с Python на Go) в единый Ratatoskr-go — оркестратор конвейера Ratatoskr (порт с Python на Go) в единый
статический бинарь (CGO_ENABLED=0). Субагенты запускаются через внешний процесс бинарь. Субагенты (analyst/dev/reviewer) запускаются через внешний процесс
[opencode](https://opencode.ai). Взаимодействие — Telegram-бот. [opencode](https://opencode.ai) (v2 HTTP API). Интерфейсы: Telegram-бот + Fyne UI.
Linux — статический headless-бинарь (CGO_ENABLED=0); Windows — с Fyne-окном (cgo).
## Структура (модули internal/) ## Структура (модули internal/)
``` ```
cmd/ratatoskr/ точка входа, сборка бинаря; main.version и main.updateToken вшиваются ldflag'ом cmd/ratatoskr/ точка входа; флаги: -config, -version, -noui (headless). main.version/updateToken вшиваются ldflag'ом
internal/ internal/
app/ composition root/DI: App.New -> config.Load+Validate, ResolveExePaths, storage, Runner, Analyst, Core, Worker, Updater. packageOwner="kamelion", Version="0.1.0" app/ composition root/DI: App.New -> config.Load+Validate, ResolveExePaths, storage, Runner, Analyst, Core, Worker, Updater, UI. packageOwner="kamelion", Version="0.2.2"
config/ YAML+env загрузка (${VAR:-default}), defaults, validate C1-C4 config/ YAML+env загрузка (${VAR:-default}), defaults, validate C1-C4
model/ доменные типы/контракты: Status + validTransitions (IsValidTransition/IsTerminal), TraceStatus. Нижний слой — не зависит от storage/events
chat/ мультиканальный Router; telegram — long-poll канал. Коды M1-M5 chat/ мультиканальный Router; telegram — long-poll канал. Коды M1-M5
core/ state-machine задач + Decider/analyst интерфейс. Коды D3/D4 core/ state-machine задач + Decider/analyst интерфейс. Коды D3/D4
analyst/ аналитик: промпт + разбор JSON-вердикта (opencode agent). Коды A1-A4 analyst/ аналитик: промпт + разбор JSON-вердикта (opencode agent). Коды A1-A4
events/ шина событий UI: Bus (buffered Sub/Pub), типизированный Hub, события TaskCreated/TaskStatusChanged/HistoryAppended/TraceAppended/AgentActivity, LogBus (панель «Логи»)
worker/ polling-планировщик + dev/reviewer-конвейер + gitops. Коды W*, E*, R* worker/ polling-планировщик + dev/reviewer-конвейер + gitops. Коды W*, E*, R*
agents/ встроенные opencode-агенты (analyst.md, dev.md, reviewer.md) через go:embed agents/ встроенные opencode-агенты (analyst.md, dev.md, reviewer.md) через go:embed
opencode/ HTTP-клиент v2 API opencode serve, поллинг вердикта, LiveRegistry. Коды O1-O5 opencode/ HTTP-клиент v2 API opencode serve, поллинг вердикта, LiveRegistry. Коды O1-O5
storage/ SQLite (modernc.org/sqlite, без CGO): tasks, traces, task_history. Коды S1-S5 storage/ SQLite (modernc.org/sqlite, без CGO): tasks, traces, task_history. Коды S1-S5
update/ автообновление из Gitea Packages. Коды U1-U6 update/ автообновление из Gitea Packages. Коды U1-U6
ui/ desktop-оболочка: Store-абстракция (копии), Commands (UI->Core), Controller (шины->View), View/Window интерфейсы
ui/desktop/ Fyne-реализация окна (build-tag cgo, --noui / нет cgo → headless)
scripts/ build-publish-ui.ps1 — локальная Windows-сборка Fyne-бинаря + публикация в Gitea
docs/ ui-spec.md — спека Fyne UI (слои, event-bus, fyne.Do/снапшоты)
``` ```
## Ключевые инварианты ## Ключевые инварианты
- **Команды/ядро:** Core.ProcessTurn — state-machine поверх storage. Команды: - **App.New-сигнатура:** `App.New(configPath, version, updateToken string, noUI bool)` (4-й параметр — headless; cgo-вариант собирается только при наличии C-компилятора).
/start /cancel /skip /retry N /status N /continue N. Активная задача — одна на чат. - **UI:** окно — ещё одна реализация `chat.Channel` (присоединяется в Router). Core не трогает UI; обмен — событийная шина (events). Кнопка «Завершить» = полный выход (SetOnQuit→cancel→UI.Run возвращается); закрытие крестиком = сворачивание, Core живёт. UI собирается с `--noui`/без cgo.
- **Фазы аналитика (Decision.Phase):** `ask` (уточняющие вопросы), `propose` (правки - **Фазы аналитика (Decision.Phase):** `ask`, `propose`, `ready` (два последних обрабатываются одинаково в core), `abort`. Требования валидатора: ask — chat_reply/questions; propose — хотя бы одно изменённое поле; ready — без изменённых полей.
черновика), `ready` (черновик полон как есть), `abort` (тема вне проекта). propose и ready - **Статусы задач (internal/model):** draft→collecting→ready→approved→running→success/failed/timeout + cancelled/aborted/closed (терминальные). UserID — chat.ID (одна активная задача на чат).
в core обрабатываются одинаково. - **Decider/Worker/Analyst/Reviewer:** Decider=analyst интерфейс; Worker — polling-планировщик; Reviewer проверяет diff dev-ветки (R1-R6), вердикт JSON {passed, critical_issues, solid_violations, comments}.
- **Статусы задач:** draft→collecting→ready→approved→running→success/failed/timeout, - **gitops (worker):** worktree-режим; feature-ветка `feat/<taskTag>` от origin/main; push через http.extraHeader, токен Bearer.
плюс cancelled/aborted/closed. approved — финальное одобрение («создавай»), после чего - **Пути «всё рядом с .exe»:** db/worktree резолвятся от ExeDir; config.yaml — рядом с бинарём, фоллбэк cwd.
воркер берёт задачу. IsValidTransition/IsTerminal в storage/models.go. - **Автообновление:** авто = только Check+уведомление; замена — по /update; версии в `commit-<sha7>/` (не `latest/`); Verify сверяет предprod-версию (binary+в.в) .
- **Decider/Worker/Analyst/Reviewer:** Decider=analyst интерфейс (analyst пакет реализует);
Worker — polling-планировщик dev-агента; Reviewer проверяет diff dev-ветки (R1-R6),
вердикт JSON {passed, critical_issues, solid_violations, comments}.
- **gitops (worker):** worker работает с worktrees; feature-ветка = `feat/<taskTag>` от
origin/main; ensureBranch (reset --hard + clean -fd + checkout -B), branchDiff
(`origin/main...<branch>`), push через http.extraHeader, токен как Bearer.
- **Пути «всё рядом с .exe»:** относительные db/worktree резолвятся от каталога бинаря
(ExeDir), не от cwd. config.yaml ищутся рядом с бинарём, фоллбэк cwd.
- **Автообновление:** авто = только Check+уведомление; замена — по /update. Версии в
Gitea Packages `commit-<sha7>/` (не `latest/`). Вердикты агентов — строгий JSON.
## opencode (v2 HTTP API, >= 1.18.18) ## opencode (v2 HTTP API, >= 1.18.18)
- Интеграция с субагентами — через headless `opencode serve`, **v2 API** (префикс `/api/*`). - Интеграция с субагентами — через headless `opencode serve`, **v2 API** (`/api/*`). Версия opencode >= 1.18.18.
Минимальная версия opencode **>= 1.18.18** (старый бинарь — только `/global/health`, не годится; - **Хардпин модели:** при создании сессии читается top-level `model` из конфига opencode (`internal/opencode/config.go`, JSONC-стрип) и передаётся в `POST /api/session` как `{"model":{providerID,id}}`.
healthcheck падает с понятной ошибкой, класс O1). - **О5 WARN (устойчивость к v1-конфигу):** конфиг по старой схеме молча игнорируется v2; провайдер без api → unsupported модели → fallback. Ratatoskr не чинит сам, но логирует warning; фактическая модель ответа сравнивается с ожидаемой. Правильный v2-вид: `api:{type:"aisdk",package,url}`, `request.headers` вместо `options.headers`.
- **Хардпин модели:** при создании сессии читается top-level `"model"` из конфига opencode - **Поллинг вердикта:** `POST /api/session/:id/prompt` (durable admit) → `GET /api/session/:id/message?order=desc&limit=200` (новые assistant-сообщения, текст в `content[].type=="text"`) → завершение = `GET /api/session/active` без сессии + финальное assistant-сообщение, стабильное `settlePolls=2` опроса. `POST .../interrupt` вместо abort.
(`internal/opencode/config.go`, JSONC-стрип `//`/`/* */`/trailing-запятых) и передаётся в
`POST /api/session` как `{"model":{providerID,id}}` (разбор `provider/id` по первому `/`).
- **Класс O5 WARN (устойчивость к v1-конфигу):** конфиг по старой v1-схеме
(`provider.X.npm`/`options`) молча игнорируется v2 → провайдер без api → модель unsupported →
fallback. Раtatoskr не чинит это сам, но логирует warning: конфиг не читается/нет `model`,
и/или фактическая модель ответа (из assistant-сообщения) ≠ ожидаемой. Правильный v2-вид:
`api:{type:"aisdk",package,url}` и `request.headers` вместо `options.headers`.
- **Поллинг вердикта:** `POST /api/session/:id/prompt` (durable admit, неблокирующий) →
`GET /api/session/:id/message?order=desc&limit=200` (новые assistant-сообщения, текст в
`content[].type=="text"`) → завершение = `GET /api/session/active` без сессии + финальное
assistant-сообщение, стабильное `settlePolls=2` опроса. `POST .../interrupt` вместо abort.
- `ModelRef{ProviderID,ID,Variant}` — аналог v2 Model.Ref; `MinVersion="1.18.18"` в server.go.
## Контракты (не ломать) ## Контракты (не ломать)
- `App.New(configPath, version, updateToken string)` — сигнатура. - `App.New(configPath, version, updateToken string, noUI bool)` — сигнатура.
- `packageOwner` — константа "kamelion" (не плодить vars/ldflag/конфиг). - `packageOwner` — константа "kamelion" (не плодить vars/ldflag/конфиг).
- `update.Updater` — создаётся структурой `&update.Updater{...}`, конструктора нет. - `update.Updater` — создаётся структурой `&update.Updater{...}`, конструктора нет.
- `ui.Store` — чтение-модель для UI: ListTasks/GetTask/GetHistory/GetTraces (копии, без ссылок на storage).
- `events.Bus`/`events.LogBus` — шина UI (см. internal/events). publisher-интерфейсы подключаются к Core/Worker.
См. также `mem:tech_stack`, `mem:conventions`, `mem:task_completion`, `mem:suggested_commands`. см. также `mem:tech_stack`, `mem:conventions`, `mem:task_completion`, `mem:suggested_commands`.

View File

@@ -6,22 +6,27 @@
- `go fmt ./...` (или `make fmt`). - `go fmt ./...` (или `make fmt`).
- `go build -o ratatoskr ./cmd/ratatoskr/` (или `make build`). - `go build -o ratatoskr ./cmd/ratatoskr/` (или `make build`).
- `./ratatoskr -config config.yaml` (или `make run`). - `./ratatoskr -config config.yaml` (или `make run`).
- `./ratatoskr -version` показать версию бинаря. - `./ratatoskr -version` — версия бинаря; `./ratatoskr -noui` — headless (без окна).
- Кросс-сборка: `make cross` (linux/amd64 + windows/amd64). - Кросс-сборка: `make cross` (linux/amd64 + windows/amd64, headless).
## Windows / UI (Fyne)
- Сборка с UI (cgo): `powershell -ExecutionPolicy Bypass -File scripts/build-publish-ui.ps1`.
Требует `scripts/.gitea-creds` (не коммитить) и MinGW/gcc в PATH. См. `mem:toolchain/cgo-winlibs-gcc`.
- Обычная headless-сборка на Windows без cgo: `go build -o ratatoskr.exe ./cmd/ratatoskr/`.
## Go-тулчейн ## Go-тулчейн
Хост без `go` в PATH — экспорт вручную: Хост без `go` в PATH — экспорт вручную:
``` ```
export PATH=/opt/data/.local/go/bin:$PATH export PATH=/opt/data/.local/go/bin:$PATH
``` ```
На машине разработки (Windows, cmd) — обычный system Go. На машине разработки (Windows) — обычный system Go.
## git (worktree-процесс Ratatoskr) ## git (worktree-процесс Ratatoskr)
- Feature-ветка: `feat/<taskTag>`, база — `origin/main`. Пример проверки diff всей ветки: - Feature-ветка: `feat/<taskTag>`, база — `origin/main`. Проверка diff всей ветки:
`git diff origin/main...HEAD`. `git diff origin/main...HEAD`.
- Проверить состав отслеживаемых файлов (например наличие .serena): `git ls-files`. - Проверить состав отслеживаемых файлов (например наличие .serena): `git ls-files`.
- Статус: `git status`. Лог: `git log --oneline -10`. - Статус: `git status`. Лог: `git log --oneline -10`.
## Замечания про среду ## Замечания про среду
- ОС Windows + PowerShell 5.1 (shell: powershell) — команды собирать/запускать с учётом - ОС Windows + PowerShell 5.1 (shell: powershell) — нет `&&`; использовать `;`/`if ($?) {}`;
этом (нет `&&`; использовать `;`/`if ($?) {}`; & для путей с пробелами). `&` для вызова путей с пробелами. Для серенных файлов лучше отдельная команда `serena memories check`.

View File

@@ -6,9 +6,10 @@
2. **Тесты:** `go test ./... -v -count=1 -timeout 120s` — все проходят (`make test`). 2. **Тесты:** `go test ./... -v -count=1 -timeout 120s` — все проходят (`make test`).
3. **Статический анализ:** `go vet ./...` — чисто (`make vet`). 3. **Статический анализ:** `go vet ./...` — чисто (`make vet`).
4. **Сборка:** `go build -o ratatoskr ./cmd/ratatoskr/` (`make build`) — компилируется. 4. **Сборка:** `go build -o ratatoskr ./cmd/ratatoskr/` (`make build`) — компилируется.
(Кросс-сборка `make cross` — только при необходимости.) - UI-сборка (cgo+Fyne) проверяется отдельно (`scripts/build-publish-ui.ps1` или `go build` с MinGW);
headless-сборка cgo-пакетов (`internal/ui/desktop`) — только typecheck.
5. **Пересмотр контрактов:** если менялась сигнатура `App.New(configPath, version, 5. **Пересмотр контрактов:** если менялась сигнатура `App.New(configPath, version,
updateToken string)` — обновить вызовы в `internal/app/app_test.go` (иначе go vet падает). updateToken string, noUI bool)` — обновить все вызовы (app_test.go, main.go).
6. Коммит осмысленными атомарными коммитами в feature-ветку `feat/<taskTag>` от origin/main. 6. Коммит осмысленными атомарными коммитами в feature-ветку `feat/<taskTag>` от origin/main.
Рабочий процесс Ratatoskr (агент dev в этом конвейере): изучить код, реализовать так, чтобы Рабочий процесс Ratatoskr (агент dev в этом конвейере): изучить код, реализовать так, чтобы

View File

@@ -1,32 +1,32 @@
# tech_stack # tech_stack
## Язык / рантайм ## Язык / рантайм
- Go **1.25.0** (go.mod `go 1.25.0`). Модуль `github.com/kamelion/ratatoskr-go`. - Go **1.25.0** (go.mod `go 1.25.0`, module `github.com/kamelion/ratatoskr-go`).
- Сборка: статический бинарь, `CGO_ENABLED=0`. Локальный Go-тулчейн на хосте: - Headless-сборка статический бинарь `CGO_ENABLED=0`. Windows-сборка с UI — cgo (Fyne/GLFW) + MinGW-w64/gcc.
`export PATH=/opt/data/.local/go/bin:$PATH` (go1.25.0 linux/amd64). - Локальный Go-тулчейн на хосте: `export PATH=/opt/data/.local/go/bin:$PATH` (go1.25.0 linux/amd64). На Windows — обычный system Go.
## Основные зависимости (go.mod) ## Основные зависимости (go.mod)
- `fyne.io/fyne/v2 v2.6.0` — desktop UI (cgo/GLFW; платформа Windows + Linux). Не собирается в CI (headless).
- `gopkg.in/yaml.v3 v3.0.1` — парсинг config.yaml. - `gopkg.in/yaml.v3 v3.0.1` — парсинг config.yaml.
- `modernc.org/sqlite v1.56.0` — SQLite без CGO (чистый Go). - `modernc.org/sqlite v1.56.0` — SQLite без CGO (чистый Go).
- (indirect) google/uuid, go-humanize, mattn/go-isatty, x/sys, modernc.org/libc/mathutil/memory. - (indirect) google/uuid, go-humanize, go-text/typesetting, x/sys, x/net, modernc.org/libc и пр.
## Внешние процессы ## Внешние процессы
- **opencode** (opencode.ai) — внешний процесс для субагентов (analyst/dev/reviewer). - **opencode** (opencode.ai) — субагенты (analyst/dev/reviewer). Управляется через internal/opencode (Runner, Pool, LiveRegistry). Требуемая версия **>= 1.18.18** (v2 HTTP API `/api/*`).
Управляется через internal/opencode (Runner, LiveRegistry). Настраивается в конфиге - Настройки в конфиге: `opencode.bin / config / config_dir / hard_timeout / idle_timeout / poll_ms` + `opencode.server` (hostname, port, password).
(opencode.bin / config / config_dir / hard_timeout / idle_timeout / poll_ms). - Подробно — `mem:core`.
Требуемая версия opencode: **>= 1.18.18** (v2 HTTP API `/api/*`; хардпин model из конфига —
`internal/opencode/config.go`, классы O1/O5). Подробно — `mem:core`.
## Сборка / Makefile ## Сборка / Makefile
- `make build` — go build -ldflags="-s -w -X main.version=commit-<sha7> -X main.updateToken=..." -o ratatoskr ./cmd/ratatoskr/. - `make build` — go build `-ldflags="-s -w -X main.version=commit-<sha7> -X main.updateToken=..." -o ratatoskr ./cmd/ratatoskr/`.
- `make test` — go test ./... -v -count=1 -timeout 120s. - `make test``go test ./... -v -count=1 -timeout 120s`.
- `make vet` — go vet ./... `make fmt` — go fmt ./... - `make vet``go vet ./...`; `make fmt``go fmt ./...`.
- `make run` — build + ./ratatoskr -config config.yaml. - `make run` — build + `./ratatoskr -config config.yaml`.
- `make cross` кросс-сборка linux/amd64 + windows/amd64 (ratatoskr-windows-amd64.exe — Windows-машина Камиля). - `make cross` — linux/amd64 + windows/amd64 (headless, БЕЗ Fyne-UI; для UI нужен cgo).
- GIT_SHA вшивается в main.version; UPDATE_TOKEN — в main.updateToken (секрет только у CI). - **Fyne-UI (Windows) собирается вручную** на машине с MinGW:
`scripts/build-publish-ui.ps1` (cgo) — сборка + публикация в Gitea в версию `commit-<sha7>`.
Требует `scripts/.gitea-creds` (git-ignored) и gcc в PATH. См. `mem:toolchain/cgo-winlibs-gcc`.
## CI (.gitea/workflows/ci.yaml) ## CI (.gitea/workflows/ci.yaml)
- Job `test`: go test ./... + go vet ./.... - Job `test`: `go test ./...` + `go vet ./...` (setup-go '1.23', check-latest).
- Job `build-and-package` (matrix linux/amd64+windows/amd64): собирает и публикует в - Job `build-and-package` (matrix linux/amd64 + windows/amd64): собирает headless-бинарь с ldflag update_token, публикует на `main` в версию `commit-<sha7>/` (+ companion `.version`/`.sha256`).
Gitea Packages на `main` в версию `commit-<sha7>/` + companion-файлы `.version`/`.sha256`. - Секреты: `TC_GITEA_TOKEN` (write:packages), `TC_UPDATE_TOKEN` (read:package), vars `GIT_MAIN_URL`.
- Секреты: TC_GITEA_TOKEN (write:packages), TC_UPDATE_TOKEN (read:package), GIT_MAIN_URL.

View File

@@ -1,23 +1,13 @@
# C-тулчейн для cgo-сборки (Fyne/GLFW) на этой машине # cgo/winlibs-gcc
WinLibs (BrechtSanders.WinLibs.POSIX.UCRT) установлен через winget как ## MinGW/WinLibs для Fyne-сборки на Windows
portable (zip) в: - UI (internal/ui/desktop, fyne.io/fyne/v2) требует cgo + C-компилятор для GLFW.
`C:\Users\darkg\AppData\Local\Microsoft\WinGet\Packages\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\mingw64\bin` - Используется **WinLibs** (mingw64) gcc. Bin каталог нужно добавить в PATH перед
`scripts/build-publish-ui.ps1`:
- gcc 16.1.0, target `x86_64-w64-mingw32`, рантайм `x86_64-ucrt-posix-seh`. ```powershell
- Установлен портативно — НЕ регистрируется в PATH автоматически. $p = "<...>\mingw64\bin"; $env:Path = "$p;$env:Path"
- PATH добавлен в `HKCU\Environment` (WinLibs bin первым), но в новых ```
PowerShell-процессах opencode окружение НЕ перечитывается, поэтому в каждой - Проверка: `gcc --version`; также убедиться, что `CGO_ENABLED` не = "0".
команде, где нужен cgo, добавлять вручную: - Care: не использовать Cygwin/MSYS-сборки gcc — только native MinGW-w64 (WinLibs).
$p = "...\mingw64\bin"; $env:Path = "$p;$env:Path"; $env:CGO_ENABLED = "1" - Сборка UI выполняется локально на Windows-машине, публикуется в Gitea Packages
в в версию `commit-<sha7>` со companion-.version/.sha256 (контракт U4) — см. скрипт и `mem:tech_stack`.
Важно: на машине есть clang-msvc/i686 (D:\Program Files\LLVM, D:\Program
Files (x86)\LLVM) — он НЕ подходит для cgo (msvc-таргет + 32bit). Поэтому
gcc должен идти первым в PATH.
Для сборки окна: `CGO_ENABLED=1 go build ./...` (компилирует ui_cgo.go и
internal/ui/desktop). Без cgo окно не собирается — приложение headless
(ui_noui.go), флаг `--noui` в cmd/ratatoskr/main.go.
Для запуска GUI нужен реальный TG-конфиг (telegram.token/chat_id), иначе app
падает на init.

View File

@@ -3,9 +3,7 @@
Ratatoskr на Go — **оркестратор в единый статический бинарь** + субагенты через Ratatoskr на Go — **оркестратор в единый статический бинарь** + субагенты через
[opencode](https://opencode.ai) как внешний процесс. Переезд с Python-версии: [opencode](https://opencode.ai) как внешний процесс. Переезд с Python-версии:
все подсистемы (диалог, воркер, автообновление) собраны в один исполняемый все подсистемы (диалог, воркер, автообновление) собраны в один исполняемый
файл, что упрощает доставку и деплой. Бинарь по умолчанию headless файл без runtime-зависимостей (`CGO_ENABLED=0`), что упрощает доставку и деплой.
(`CGO_ENABLED=0`); графическое окно (Fyne) — опционально, собирается с cgo
(только локально, см. «Локальная сборка UI-бинаря»).
## Структура ## Структура
@@ -60,40 +58,10 @@ GOFLAGS ?= -ldflags="-s -w -X main.version=commit-$(GIT_SHA) -X main.updateToken
- `main.updateToken` = токен read:package для автообновления (см. ниже). Если не задан - `main.updateToken` = токен read:package для автообновления (см. ниже). Если не задан
(`UPDATE_TOKEN ?= ""`) — пустая встройка, тогда обновление берёт `update.token` из конфига. (`UPDATE_TOKEN ?= ""`) — пустая встройка, тогда обновление берёт `update.token` из конфига.
**Обычный `make build`/`make cross` окно НЕ включает** — Fyne-окно требует cgo и
C-компилятора. Headless-бинарь (без окна) собирается и в CI, и локально; UI-бинарь
с окном — только локально (см. ниже).
### Локальная сборка UI-бинаря (Windows, Fyne-окно) + публикация в Gitea
CI headless: `CGO_ENABLED=0` и публикует только `ratatoskr-linux-amd64`
(см. `.gitea/workflows/ci.yaml`). Windows-бинарь с окном собирается на Windows-машине
и публикуется скриптом:
```bash
# 1) Секреты (git-ignored): скопировать и заполнить
cp scripts/.gitea-creds.example scripts/.gitea-creds
# GITEA_TOKEN=write:packages-токен
# UPDATE_TOKEN=read:package-токен
# GIT_MAIN_URL=http://gitea.hal9000.home
# 2) Убедиться, что gcc (MinGW) в PATH и CGO_ENABLED=1 — см. память
# `mem:toolchain/cgo-winlibs-gcc`
# 3) Запустить (HEAD должен быть тем коммитом, что в main):
powershell -ExecutionPolicy Bypass -File scripts/build-publish-ui.ps1
```
Скрипт собирает `ratatoskr-windows-amd64.exe` с `-X main.version=commit-<sha7>` и
`-X main.updateToken=...`, пишет companion-файлы `.version`/`.sha256` и публикует все
три в `.../generic/ratatoskr/commit-<sha7>/`. В конце сам перечитывает `.sha256`/`.version`
из Gitea и сверяет (контракт U4) — как проверка в разделе «Валидация опубликованной версии».
**Как на самом деле публикуется рабочий бинарь:** локальный `go build` токен НЕ вшивает **Как на самом деле публикуется рабочий бинарь:** локальный `go build` токен НЕ вшивает
(значение секрета есть только у CI и в git-ignored `scripts/.gitea-creds`). Запушь `main` (значение секрета есть только у CI). Запушь `main` CI (см. ниже) сам соберёт оба бинаря
CI сам соберёт headless-linux со вшитым `TC_UPDATE_TOKEN` и загрузит его в Gitea Packages; со вшитым `TC_UPDATE_TOKEN` и загрузит их в Gitea Packages. Так соблюдён least-privilege:
Windows-бинарь с UI публикуется локальным скриптом (токен read:package в `scripts/.gitea-creds`). read-токен не светится на хосте разработки.
Так соблюдён least-privilege: read-токен не светится в репозитории/на хосте CI.
### Проверки перед сборкой ### Проверки перед сборкой
@@ -110,17 +78,13 @@ make run # build + ./ratatoskr -config config.yaml
## CI (.gitea/workflows/ci.yaml) ## CI (.gitea/workflows/ci.yaml)
- **Job `test`**: checkout → setup-go 1.25 `go test ./...``go vet ./...`. - **Job `test`**: checkout → setup-go → `go test ./...``go vet ./...`.
Работает в headless-режиме (`CGO_ENABLED=0`, без Fyne/glfw). - **Job `build-and-package`** (matrix linux/amd64 + windows/amd64, без артефактов):
- **Job `build-and-package`** (linux/amd64, без артефактов): собирает **headless**-бинарь собирает бинарь со вшитым `-X main.updateToken=${secrets.TC_UPDATE_TOKEN}` и
`ratatoskr-linux-amd64` со вшитым `-X main.updateToken=${secrets.TC_UPDATE_TOKEN}` и
**публикует в Gitea Packages** — только на `main`, сразу в версию `commit-<sha7>/` **публикует в Gitea Packages** — только на `main`, сразу в версию `commit-<sha7>/`
(псевдо-версия `latest` НЕ используется — см. автообновление). Плюс companion-файлы (псевдо-версия `latest` НЕ используется — см. автообновление). Плюс companion-файлы
`<file>.version` и `<file>.sha256`. `<file>.version` и `<file>.sha256`.
**Windows-бинарь с UI в CI НЕ собирается** (нужен cgo + C-компилятор). Он собирается
и публикуется локально скриптом `scripts/build-publish-ui.ps1` (см. «Локальная сборка UI-бинаря»).
Секреты/vars: `TC_GITEA_TOKEN` (write:packages), `TC_UPDATE_TOKEN` (read:package), Секреты/vars: `TC_GITEA_TOKEN` (write:packages), `TC_UPDATE_TOKEN` (read:package),
`GIT_MAIN_URL`. `GIT_MAIN_URL`.
@@ -235,12 +199,9 @@ update:
### Как опубликовать новую версию ### Как опубликовать новую версию
1. Внеси правки, коммитни, запуши в `main`. 1. Внеси правки, коммитни, запуши в `main`.
2. CI соберёт `ratatoskr-linux-amd64` (headless) со вшитым `TC_UPDATE_TOKEN` и опубликует в 2. CI соберёт оба бинаря со вшитым `TC_UPDATE_TOKEN` и опубликует в
`.../generic/ratatoskr/commit-<sha7>/` (бинарь + `.version` + `.sha256`). `.../generic/ratatoskr/commit-<sha7>/` (бинарь + `.version` + `.sha256`).
3. Если в этой версии есть изменения UI — дополнительно собрать и опубликовать 3. На обновляемой машине: бот сам уведомит о новой версии → `/update`.
Windows-бинарь с окном: `scripts/build-publish-ui.ps1` (добавит
`ratatoskr-windows-amd64.exe` в ту же версию).
4. На обновляемой машине: бот сам уведомит о новой версии → `/update`.
Проверку автообновления после публикации гоняет владелец на своей Проверку автообновления после публикации гоняет владелец на своей
Windows-машине (не прогоняется агентом). Windows-машине (не прогоняется агентом).

View File

@@ -1,6 +0,0 @@
# Секреты для scripts/build-publish-ui.ps1 — НЕ коммитить (git-ignored).
# Скопируйте этот файл в scripts/.gitea-creds и заполните реальные значения.
# Формат: по одной `ключ=значение` на строку, строки с # — комментарии.
GITEA_TOKEN=write:packages-токен
UPDATE_TOKEN=read:package-токен
GIT_MAIN_URL=http://gitea.hal9000.home