aenvs/.docs/03-opus-plans/implementation-plan.md

28 KiB
Raw Blame History

AEVS CLI — Implementation Plan

Пошаговый план для агента-разработчика.

Общая информация

Проект: AEVS CLI — инструмент синхронизации .env файлов между машинами одного пользователя.

Технологии:

  • Go (golang)
  • CLI: github.com/spf13/cobra
  • S3: github.com/aws/aws-sdk-go-v2
  • YAML: gopkg.in/yaml.v3

Документация: .docs/02-opus-cli-docs/


Фаза 1: Инициализация проекта

Шаг 1.1: Создание структуры проекта

Создай следующую структуру директорий:

aevs/
├── cmd/
│   └── aevs/
│       └── main.go           # точка входа
├── internal/
│   ├── cli/
│   │   ├── root.go           # root command
│   │   ├── config.go         # aevs config
│   │   ├── init.go           # aevs init
│   │   ├── push.go           # aevs push
│   │   ├── pull.go           # aevs pull
│   │   ├── list.go           # aevs list
│   │   └── status.go         # aevs status
│   ├── config/
│   │   ├── global.go         # GlobalConfig, загрузка/сохранение
│   │   ├── project.go        # ProjectConfig
│   │   └── constants.go      # константы
│   ├── storage/
│   │   ├── storage.go        # Storage interface
│   │   └── s3.go             # S3 implementation
│   ├── archiver/
│   │   └── archiver.go       # tar.gz создание/распаковка
│   ├── scanner/
│   │   └── scanner.go        # сканирование .env файлов
│   └── types/
│       └── types.go          # Metadata, FileStatus, etc.
├── go.mod
├── go.sum
└── README.md

Чеклист:

  • Создана директория cmd/aevs/
  • Создана директория internal/cli/
  • Создана директория internal/config/
  • Создана директория internal/storage/
  • Создана директория internal/archiver/
  • Создана директория internal/scanner/
  • Создана директория internal/types/
  • Проверен другой моделью и составлен следующий шаг

Шаг 1.2: Инициализация Go модуля

go mod init github.com/user/aevs

Чеклист:

  • Выполнена команда go mod init
  • Создан файл go.mod
  • Проверен другой моделью и составлен следующий шаг

Шаг 1.3: Добавление зависимостей

go get github.com/spf13/cobra@latest
go get github.com/aws/aws-sdk-go-v2/config@latest
go get github.com/aws/aws-sdk-go-v2/service/s3@latest
go get github.com/aws/aws-sdk-go-v2/credentials@latest
go get gopkg.in/yaml.v3@latest

Чеклист:

  • Добавлена зависимость github.com/spf13/cobra
  • Добавлена зависимость github.com/aws/aws-sdk-go-v2/config
  • Добавлена зависимость github.com/aws/aws-sdk-go-v2/service/s3
  • Добавлена зависимость github.com/aws/aws-sdk-go-v2/credentials
  • Добавлена зависимость gopkg.in/yaml.v3
  • Создан файл go.sum
  • Проверен другой моделью и составлен следующий шаг

Фаза 2: Types & Config

Шаг 2.1: Создай internal/types/types.go

Реализуй типы из документации (см. .docs/02-opus-cli-docs/04-types.md):

  • Metadata — метаданные в storage
  • ProjectInfo — для list
  • FileStatus — для status
  • SyncStatus — константы статусов

Чеклист:

  • Создан файл internal/types/types.go
  • Реализован тип Metadata
  • Реализован тип ProjectInfo
  • Реализован тип FileStatus
  • Реализован тип SyncStatus с константами
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Шаг 2.2: Создай internal/config/constants.go

Константы из документации:

  • DefaultConfigDir = ".config/aevs"
  • DefaultGlobalConfigFile = "config.yaml"
  • DefaultProjectConfigFile = "aevs.yaml"
  • ArchiveFileName = "envs.tar.gz"
  • MetadataFileName = "metadata.json"
  • и т.д.

Чеклист:

  • Создан файл internal/config/constants.go
  • Определена константа DefaultConfigDir
  • Определена константа DefaultGlobalConfigFile
  • Определена константа DefaultProjectConfigFile
  • Определена константа ArchiveFileName
  • Определена константа MetadataFileName
  • Определены дефолтные значения для S3
  • Проверен другой моделью и составлен следующий шаг

Шаг 2.3: Создай internal/config/global.go

type GlobalConfig struct {
    Storage StorageConfig `yaml:"storage"`
}

type StorageConfig struct {
    Type      string `yaml:"type"`
    Endpoint  string `yaml:"endpoint"`
    Region    string `yaml:"region"`
    Bucket    string `yaml:"bucket"`
    AccessKey string `yaml:"access_key"`
    SecretKey string `yaml:"secret_key"`
}

Функции:

  • LoadGlobalConfig() (*GlobalConfig, error) — загрузка из ~/.config/aevs/config.yaml
  • SaveGlobalConfig(cfg *GlobalConfig) error — сохранение с правами 0600
  • GlobalConfigPath() string — путь к конфигу
  • GlobalConfigExists() bool — проверка существования

Чеклист:

  • Создан файл internal/config/global.go
  • Реализован тип GlobalConfig
  • Реализован тип StorageConfig
  • Реализована функция LoadGlobalConfig()
  • Реализована функция SaveGlobalConfig() с правами 0600
  • Реализована функция GlobalConfigPath()
  • Реализована функция GlobalConfigExists()
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Шаг 2.4: Создай internal/config/project.go

type ProjectConfig struct {
    Project string   `yaml:"project"`
    Files   []string `yaml:"files"`
}

Функции:

  • LoadProjectConfig(path string) (*ProjectConfig, error)
  • SaveProjectConfig(path string, cfg *ProjectConfig) error
  • ValidateProjectName(name string) error — проверка [a-z0-9_-]

Чеклист:

  • Создан файл internal/config/project.go
  • Реализован тип ProjectConfig
  • Реализована функция LoadProjectConfig()
  • Реализована функция SaveProjectConfig()
  • Реализована функция ValidateProjectName() с regex [a-z0-9_-]
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Фаза 3: Storage Layer

Шаг 3.1: Создай internal/storage/storage.go

Интерфейс Storage:

type Storage interface {
    Upload(ctx context.Context, key string, data io.Reader, size int64) error
    Download(ctx context.Context, key string) (io.ReadCloser, error)
    Delete(ctx context.Context, key string) error
    Exists(ctx context.Context, key string) (bool, error)
    List(ctx context.Context, prefix string) ([]string, error)
    ListProjects(ctx context.Context) ([]string, error)
    TestConnection(ctx context.Context) error
}

Чеклист:

  • Создан файл internal/storage/storage.go
  • Определён интерфейс Storage
  • Метод Upload в интерфейсе
  • Метод Download в интерфейсе
  • Метод Delete в интерфейсе
  • Метод Exists в интерфейсе
  • Метод List в интерфейсе
  • Метод ListProjects в интерфейсе
  • Метод TestConnection в интерфейсе
  • Проверен другой моделью и составлен следующий шаг

Шаг 3.2: Создай internal/storage/s3.go

S3 реализация интерфейса Storage:

type S3Storage struct {
    client *s3.Client
    bucket string
}

func NewS3Storage(cfg *config.StorageConfig) (*S3Storage, error)

Методы:

  • Uploads3.PutObject
  • Downloads3.GetObject
  • Deletes3.DeleteObject
  • Existss3.HeadObject
  • Lists3.ListObjectsV2
  • ListProjects — list с delimiter /
  • TestConnections3.ListBuckets или HeadBucket

Важно: Используй aws.Config с кастомным endpoint для поддержки MinIO, R2, Spaces.

Чеклист:

  • Создан файл internal/storage/s3.go
  • Реализована структура S3Storage
  • Реализована функция NewS3Storage() с кастомным endpoint
  • Реализован метод Upload()
  • Реализован метод Download()
  • Реализован метод Delete()
  • Реализован метод Exists()
  • Реализован метод List()
  • Реализован метод ListProjects() с delimiter
  • Реализован метод TestConnection()
  • S3Storage реализует интерфейс Storage
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Фаза 4: Scanner & Archiver

Шаг 4.1: Создай internal/scanner/scanner.go

Сканирование директории на .env файлы:

func Scan(rootDir string) ([]string, error)

Паттерны для включения:

  • .env
  • .env.*
  • *.env

Исключения (директории):

  • node_modules, .git, vendor, venv, .venv, __pycache__, .idea, .vscode, dist, build

Исключения (файлы):

  • .env.example, .env.sample, .env.template

Чеклист:

  • Создан файл internal/scanner/scanner.go
  • Определены паттерны включения
  • Определены исключаемые директории
  • Определены исключаемые файлы
  • Реализована функция Scan()
  • Функция рекурсивно обходит директории
  • Функция корректно фильтрует файлы
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Шаг 4.2: Создай internal/archiver/archiver.go

func Create(files []string, rootDir string) (io.Reader, int64, error)
func Extract(archive io.Reader, destDir string) ([]string, error)
func List(archive io.Reader) ([]string, error)
  • Create — создаёт tar.gz архив из списка файлов
  • Extract — распаковывает архив в директорию, создаёт недостающие папки
  • List — возвращает список файлов в архиве без распаковки

Чеклист:

  • Создан файл internal/archiver/archiver.go
  • Реализована функция Create() (tar.gz)
  • Реализована функция Extract() с созданием директорий
  • Реализована функция List()
  • Пути в архиве сохраняются относительно rootDir
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Фаза 5: CLI Commands

Шаг 5.1: Создай internal/cli/root.go

Root command с cobra:

var rootCmd = &cobra.Command{
    Use:   "aevs",
    Short: "Sync .env files between machines",
}

func Execute() error {
    return rootCmd.Execute()
}

Глобальные флаги:

  • --verbose, -v — verbose output
  • --debug — debug output

Чеклист:

  • Создан файл internal/cli/root.go
  • Определён rootCmd с cobra
  • Реализована функция Execute()
  • Добавлен флаг --verbose / -v
  • Добавлен флаг --debug
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Шаг 5.2: Создай internal/cli/config.go — команда aevs config

Интерактивная настройка credentials:

  1. Запросить storage type (default: s3)
  2. Запросить endpoint (default: https://s3.amazonaws.com)
  3. Запросить region (default: us-east-1)
  4. Запросить bucket name
  5. Запросить access key
  6. Запросить secret key (скрытый ввод)
  7. Проверить подключение (TestConnection)
  8. Сохранить конфиг

Чеклист:

  • Создан файл internal/cli/config.go
  • Определён configCmd с cobra
  • Реализован интерактивный ввод storage type
  • Реализован интерактивный ввод endpoint
  • Реализован интерактивный ввод region
  • Реализован интерактивный ввод bucket name
  • Реализован интерактивный ввод access key
  • Реализован скрытый ввод secret key
  • Вызывается TestConnection() для проверки
  • Конфиг сохраняется через SaveGlobalConfig()
  • Команда зарегистрирована в rootCmd
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Шаг 5.3: Создай internal/cli/init.go — команда aevs init

aevs init [project-name]

Флаги:

  • --force, -f — перезаписать существующий конфиг

Логика:

  1. Проверить существование aevs.yaml
  2. Если существует и нет --force:
    • Сканировать на новые файлы
    • Предложить добавить
  3. Если не существует:
    • Сканировать директорию
    • Определить имя проекта (аргумент или имя папки)
    • Создать aevs.yaml

Чеклист:

  • Создан файл internal/cli/init.go
  • Определён initCmd с cobra
  • Добавлен флаг --force / -f
  • Реализована проверка существования aevs.yaml
  • Реализовано сканирование директории через scanner.Scan()
  • Реализовано определение имени проекта (аргумент или имя папки)
  • Реализовано добавление новых файлов в существующий конфиг
  • Создаётся aevs.yaml через SaveProjectConfig()
  • Команда зарегистрирована в rootCmd
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Шаг 5.4: Создай internal/cli/push.go — команда aevs push

Флаги:

  • --config, -c — путь к конфигу
  • --dry-run — показать что будет загружено

Логика:

  1. Загрузить глобальный конфиг
  2. Загрузить проектный конфиг
  3. Проверить существование всех файлов
  4. Создать tar.gz архив
  5. Загрузить архив в {project}/envs.tar.gz
  6. Создать и загрузить metadata.json
  7. Вывести результат

Чеклист:

  • Создан файл internal/cli/push.go
  • Определён pushCmd с cobra
  • Добавлен флаг --config / -c
  • Добавлен флаг --dry-run
  • Реализована загрузка глобального конфига
  • Реализована загрузка проектного конфига
  • Реализована проверка существования файлов
  • Реализовано создание архива через archiver.Create()
  • Реализована загрузка архива через storage.Upload()
  • Реализовано создание и загрузка metadata.json
  • Реализован режим --dry-run
  • Команда зарегистрирована в rootCmd
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Шаг 5.5: Создай internal/cli/pull.go — команда aevs pull

aevs pull [project-name]

Флаги:

  • --config, -c
  • --force, -f — перезаписать без подтверждения
  • --dry-run

Логика:

  1. Определить имя проекта (аргумент или из aevs.yaml)
  2. Скачать metadata.json
  3. Скачать envs.tar.gz
  4. Для каждого файла:
    • Если не существует — создать
    • Если существует и отличается — спросить (или --force)
  5. Вывести результат

Обработка конфликтов:

  • [o]verwrite — перезаписать
  • [s]kip — пропустить
  • [d]iff — показать diff
  • [O]verwrite all
  • [S]kip all

Чеклист:

  • Создан файл internal/cli/pull.go
  • Определён pullCmd с cobra
  • Добавлен флаг --config / -c
  • Добавлен флаг --force / -f
  • Добавлен флаг --dry-run
  • Реализовано определение имени проекта
  • Реализовано скачивание metadata.json
  • Реализовано скачивание envs.tar.gz
  • Реализована распаковка через archiver.Extract()
  • Реализована обработка конфликтов (o/s/d/O/S)
  • Реализован режим --force
  • Реализован режим --dry-run
  • Команда зарегистрирована в rootCmd
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Шаг 5.6: Создай internal/cli/list.go — команда aevs list

Флаги:

  • --json — вывод в JSON

Логика:

  1. Загрузить глобальный конфиг
  2. Получить список проектов (ListProjects)
  3. Для каждого проекта загрузить metadata.json
  4. Вывести таблицу или JSON

Чеклист:

  • Создан файл internal/cli/list.go
  • Определён listCmd с cobra
  • Добавлен флаг --json
  • Реализована загрузка глобального конфига
  • Реализовано получение списка проектов через ListProjects()
  • Реализована загрузка metadata.json для каждого проекта
  • Реализован вывод в виде таблицы
  • Реализован вывод в формате JSON
  • Команда зарегистрирована в rootCmd
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Шаг 5.7: Создай internal/cli/status.go — команда aevs status

Флаги:

  • --config, -c

Логика:

  1. Загрузить оба конфига
  2. Скачать metadata.json из storage
  3. Для каждого файла:
    • Сравнить размер и хеш локального vs remote
    • Определить статус
  4. Вывести таблицу статусов

Чеклист:

  • Создан файл internal/cli/status.go
  • Определён statusCmd с cobra
  • Добавлен флаг --config / -c
  • Реализована загрузка обоих конфигов
  • Реализовано скачивание metadata.json
  • Реализовано сравнение локальных и remote файлов
  • Реализовано определение статуса каждого файла
  • Реализован вывод таблицы статусов
  • Команда зарегистрирована в rootCmd
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Фаза 6: Error Handling

Шаг 6.1: Создай internal/errors/errors.go

Определённые ошибки из документации:

var (
    ErrNoGlobalConfig  = errors.New("no storage configured; run 'aevs config' first")
    ErrNoProjectConfig = errors.New("no aevs.yaml found; run 'aevs init' first")
    ErrConfigExists    = errors.New("aevs.yaml already exists; use --force to overwrite")
    ErrInvalidProject  = errors.New("invalid project name; use only a-z, 0-9, -, _")
    ErrProjectNotFound = errors.New("project not found in storage")
    ErrAccessDenied    = errors.New("access denied; check your credentials")
    ErrBucketNotFound  = errors.New("bucket not found")
    ErrFileNotFound    = errors.New("file not found")
    ErrNoEnvFiles      = errors.New("no env files found")
)

Чеклист:

  • Создан файл internal/errors/errors.go
  • Определена ошибка ErrNoGlobalConfig
  • Определена ошибка ErrNoProjectConfig
  • Определена ошибка ErrConfigExists
  • Определена ошибка ErrInvalidProject
  • Определена ошибка ErrProjectNotFound
  • Определена ошибка ErrAccessDenied
  • Определена ошибка ErrBucketNotFound
  • Определена ошибка ErrFileNotFound
  • Определена ошибка ErrNoEnvFiles
  • Код компилируется без ошибок
  • Проверен другой моделью и составлен следующий шаг

Шаг 6.2: Exit codes

Реализуй exit codes:

  • 0 — Success
  • 1 — General error
  • 2 — Configuration error
  • 3 — File system error
  • 4 — Storage error
  • 5 — Network error
  • 130 — Interrupted

Чеклист:

  • Определены константы exit codes
  • Реализована функция для определения exit code по типу ошибки
  • Exit codes используются в main.go
  • Проверен другой моделью и составлен следующий шаг

Фаза 7: Entry Point

Шаг 7.1: Создай cmd/aevs/main.go

package main

import (
    "os"
    "aevs/internal/cli"
)

func main() {
    if err := cli.Execute(); err != nil {
        os.Exit(1)
    }
}

Чеклист:

  • Создан файл cmd/aevs/main.go
  • Импортирован пакет cli
  • Вызывается cli.Execute()
  • Используются корректные exit codes
  • Приложение компилируется: go build ./cmd/aevs
  • Приложение запускается: ./aevs --help
  • Проверен другой моделью и составлен следующий шаг

Фаза 8: Тестирование

Шаг 8.1: Unit tests

Создай тесты для:

  • config/ — загрузка/сохранение конфигов
  • scanner/ — сканирование файлов
  • archiver/ — создание/распаковка архивов
  • storage/ — mock тесты для S3

Чеклист:

  • Создан файл internal/config/global_test.go
  • Создан файл internal/config/project_test.go
  • Создан файл internal/scanner/scanner_test.go
  • Создан файл internal/archiver/archiver_test.go
  • Создан файл internal/storage/s3_test.go (mock)
  • Все тесты проходят: go test ./...
  • Проверен другой моделью и составлен следующий шаг

Шаг 8.2: Integration tests

Тесты с реальным S3 (или MinIO в Docker):

  • Полный цикл: config → init → push → pull → status → list

Чеклист:

  • Настроен MinIO в Docker для тестов
  • Создан файл интеграционных тестов
  • Тест полного цикла работает
  • Тесты можно запустить локально
  • Проверен другой моделью и составлен следующий шаг

Фаза 9: Build & Release

Шаг 9.1: Makefile

.PHONY: build test clean

build:
	go build -o bin/aevs ./cmd/aevs

test:
	go test ./...

clean:
	rm -rf bin/

Чеклист:

  • Создан файл Makefile
  • Добавлена цель build
  • Добавлена цель test
  • Добавлена цель clean
  • make build работает
  • make test работает
  • Проверен другой моделью и составлен следующий шаг

Шаг 9.2: README.md

Создай README с:

  • Описанием проекта
  • Установкой
  • Quick start
  • Командами

Чеклист:

  • Создан файл README.md
  • Добавлено описание проекта
  • Добавлены инструкции по установке
  • Добавлен раздел Quick Start
  • Добавлено описание всех команд
  • Добавлены примеры использования
  • Проверен другой моделью и составлен следующий шаг

Порядок реализации (приоритет)

  1. Фаза 1 — структура проекта
  2. Фаза 2 — types & config
  3. Фаза 4 — scanner & archiver (можно тестировать локально)
  4. Фаза 3 — storage layer
  5. Фаза 5 — CLI commands в порядке:
    • root.go
    • config.go (нужен для всего остального)
    • init.go
    • push.go
    • pull.go
    • list.go
    • status.go
  6. Фаза 6 — error handling (можно делать параллельно)
  7. Фаза 7 — entry point
  8. Фаза 8 — тесты
  9. Фаза 9 — build & docs

Ссылки на документацию