feat: встроенные агенты opencode (analyst, dev) через go:embed, распаковка по умолчанию в ./agents рядом с бинарём
All checks were successful
CI / test (push) Successful in 38s
CI / build-and-package (amd64, darwin) (push) Successful in 34s
CI / build-and-package (amd64, linux) (push) Successful in 33s
CI / build-and-package (amd64, windows) (push) Successful in 35s
CI / build-and-package (arm64, darwin) (push) Successful in 35s
CI / build-and-package (arm64, linux) (push) Successful in 34s
All checks were successful
CI / test (push) Successful in 38s
CI / build-and-package (amd64, darwin) (push) Successful in 34s
CI / build-and-package (amd64, linux) (push) Successful in 33s
CI / build-and-package (amd64, windows) (push) Successful in 35s
CI / build-and-package (arm64, darwin) (push) Successful in 35s
CI / build-and-package (arm64, linux) (push) Successful in 34s
This commit is contained in:
32
internal/agents/agents.go
Normal file
32
internal/agents/agents.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Package agents — встроенные агенты opencode (analyst, dev, ...).
|
||||
//
|
||||
// Агенты зашиты в бинарь через go:embed и распаковываются при запуске
|
||||
// в каталог OPENCODE_CONFIG_DIR (по умолчанию ./agents рядом с бинарём),
|
||||
// чтобы opencode находил их независимо от того, где запущен ratatoskr.
|
||||
package agents
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *.md
|
||||
var FS embed.FS
|
||||
|
||||
// Names — имена распаковываемых агентов (файлы *.md, без .md).
|
||||
//
|
||||
// Держим в курсе: если добавить файл в каталог, он автоматически попадёт
|
||||
// в FS, но для детерминированной распаковки список лучше дополнять здесь.
|
||||
var Names = []string{"analyst", "dev"}
|
||||
|
||||
// WriteTo распаковывает всех встроенных агентов в каталог dir (создаёт его).
|
||||
// Файлы перезаписываются — встроенная копия всегда актуальна.
|
||||
func WriteTo(dir string) error {
|
||||
for _, name := range Names {
|
||||
data, err := FS.ReadFile(name + ".md")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile(dir, name+".md", data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
25
internal/agents/agents_test.go
Normal file
25
internal/agents/agents_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteTo(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := WriteTo(dir); err != nil {
|
||||
t.Fatalf("WriteTo: %v", err)
|
||||
}
|
||||
for _, name := range Names {
|
||||
p := filepath.Join(dir, name+".md")
|
||||
data, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", p, err)
|
||||
}
|
||||
if !strings.Contains(string(data), "name: "+name) {
|
||||
t.Errorf("%s: нет frontmatter name: %s", p, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
35
internal/agents/analyst.md
Normal file
35
internal/agents/analyst.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: analyst
|
||||
description: Аналитик Ratatoskr — помогает сформулировать задачу разработки и возвращает структурированное решение в JSON
|
||||
---
|
||||
|
||||
Ты — аналитик в конвейере Ratatoskr. Пользователь ставит задачу разработки. Ты помогаешь
|
||||
продумать её до уровня готового задания для агента, который пишет код.
|
||||
|
||||
Тебе приходит промпт, в котором есть:
|
||||
- переписка с пользователем (что уже обсудили);
|
||||
- текущий черновик задачи (заполненные поля — title, goal, repo, why, ac; пустые — не заданы);
|
||||
- режим принудительного предложения (если включён — не задавать вопросы, сразу предложить черновик).
|
||||
|
||||
ПРАВИЛА:
|
||||
1. Пока неясны цель, репозиторий или критерии готовности — задай до 3 уточняющих вопросов
|
||||
(phase="ask"). Не задавай лишних вопросов, если поле уже заполнено.
|
||||
2. Когда данных достаточно (понятны цель, репозиторий, ≥2–3 проверяемых критерия) —
|
||||
phase="propose" и в chat_reply краткое резюме черновика.
|
||||
3. Если тема явно не про разработку кода или не подходит для агента-кодера — phase="abort"
|
||||
с причиной.
|
||||
4. Возвращай ВСЕГДА строго один JSON-объект без markdown-обрамления и без лишнего текста.
|
||||
5. Меняй в JSON только те поля, которые надо изменить; незаполненные — пустой строкой.
|
||||
|
||||
Формат ответа:
|
||||
{
|
||||
"phase": "ask|propose|abort",
|
||||
"title": "",
|
||||
"goal": "",
|
||||
"repo": "",
|
||||
"why": "",
|
||||
"ac": "",
|
||||
"questions": ["вопрос 1", "вопрос 2"],
|
||||
"chat_reply": "твой ответ пользователю (на русском, естественно)",
|
||||
"abort_reason": "причина, если phase=abort, иначе пусто"
|
||||
}
|
||||
16
internal/agents/dev.md
Normal file
16
internal/agents/dev.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: dev
|
||||
description: Разработчик Ratatoskr — реализует задачу в репозитории по готовым критериям
|
||||
---
|
||||
|
||||
Ты — dev-агент в конвейере Ratatoskr. Получаешь готовое задание и реализуешь его в коде.
|
||||
|
||||
Тебе приходит промпт с задачей: название, цель, зачем, репозиторий, критерии готовности (AC).
|
||||
|
||||
ПРАВИЛА:
|
||||
1. Изучи существующий код репозитория, прежде чем менять.
|
||||
2. Реализуй задачу так, чтобы все acceptance criteria были выполнены.
|
||||
3. Пользуйся инструментами opencode (чтение файлов, поиск, редактирование, запуск команд).
|
||||
4. Не удаляй код без необходимости; следуй кодстайлу проекта.
|
||||
5. Закоммить осмысленными атомарными коммитами.
|
||||
6. По окончании работы верни краткий отчёт: что сделано, какие AC закрыты, что осталось.
|
||||
18
internal/agents/embed.go
Normal file
18
internal/agents/embed.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package agents
|
||||
|
||||
// WriteTo распаковывает всех встроенных агентов в каталог dir.
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func writeFile(dir, name string, data []byte) error {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
p := filepath.Join(dir, name)
|
||||
if err := os.WriteFile(p, data, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -8,10 +8,12 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/kamelion/ratatoskr-go/internal/agents"
|
||||
"github.com/kamelion/ratatoskr-go/internal/analyst"
|
||||
"github.com/kamelion/ratatoskr-go/internal/chat"
|
||||
"github.com/kamelion/ratatoskr-go/internal/chat/telegram"
|
||||
@@ -46,6 +48,11 @@ func New(configPath string) (*App, error) {
|
||||
// SQLite для задач и трасс
|
||||
ctx := context.Background()
|
||||
|
||||
// Агенты opencode: если config_dir не задан — используем ./agents рядом с бинарём
|
||||
if err := ensureAgentsDir(cfg); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrConfig, err)
|
||||
}
|
||||
|
||||
store, err := storage.Open(ctx, cfg.Paths.DB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrDBOpen, err)
|
||||
@@ -223,3 +230,24 @@ func (a *App) send(ctx context.Context, uid chat.UserID, text string) {
|
||||
log.Printf("app: send to %s: %v", uid, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ensureAgentsDir определяет каталог с агентами opencode и распаковывает
|
||||
// туда встроенных агентов (go:embed). Если config_dir не задан — использует
|
||||
// ./agents рядом с бинарём. Встроенные агенты перезаписываются (всегда актуальны).
|
||||
//
|
||||
// idempotent: вызывается только из New.
|
||||
func ensureAgentsDir(cfg *config.Config) error {
|
||||
if cfg.OpenCode.ConfigDir == "" {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolve executable: %w", err)
|
||||
}
|
||||
// ./agents рядом с бинарём
|
||||
cfg.OpenCode.ConfigDir = filepath.Join(filepath.Dir(exe), "agents")
|
||||
}
|
||||
if err := agents.WriteTo(cfg.OpenCode.ConfigDir); err != nil {
|
||||
return fmt.Errorf("write agents to %s: %w", cfg.OpenCode.ConfigDir, err)
|
||||
}
|
||||
log.Printf("app: agents ensured in %s", cfg.OpenCode.ConfigDir)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user