From 16644fcc0be29a8e00ab04b1eed01ff8dd5b362f Mon Sep 17 00:00:00 2001 From: oqyude Date: Fri, 9 Oct 2026 20:37:14 +0300 Subject: [PATCH] =?UTF-8?q?metaagent:=20install=20v3.0.0,=20migrate=20docs?= =?UTF-8?q?/arch/*=20=E2=86=92=20.agent/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - install.sh: .agent/src/ (PROTOCOLS, COMMANDS, TEMPLATES, install.sh/ps1, GUIDE) - .temp/ добавлен в .gitignore - .agent/checkpoints.json: phases.init=completed, project_type=existing - .agent/rules/project-rules.md: R1-R5 (инварианты, ловушки, куда лезть, проверки) - .agent/context/analysis-report.md: стек, архитектура, конвенции, кандидаты CI - .agent/context/project-state.md: сжатый слепок (хосты, сервисы, ADR) - .agent/roadmap/sources.md: открытые вопросы слоёв 0-11 (бывший invariants.md) - .agent/tasks/manifest.{json,md}: 16 задач A1-F + 23 в backlog - .agent/decisions/0001-sops-secrets-paths.md: ADR для инварианта S1 - AGENTS.md: metaagent-шапка + сжатая выжимка nixos (yaml frontmatter сохранён) - удалены docs/arch/{map,invariants,todo}.md --- .agent/checkpoints.json | 36 ++ .agent/context/analysis-report.md | 119 ++++++ .agent/context/project-state.md | 107 +++++ .agent/decisions/0001-sops-secrets-paths.md | 83 ++++ .agent/decisions/index.json | 14 + .../roadmap/sources.md | 168 ++------ .agent/rules/project-rules.md | 160 +++++++ .agent/src/BOUNDARIES.md | 45 ++ .agent/src/CHANGELOG.md | 87 ++++ .agent/src/COMMANDS/adr.md | 95 +++++ .agent/src/COMMANDS/alt-arch.md | 85 ++++ .agent/src/COMMANDS/invariant-tests.md | 68 +++ .agent/src/COMMANDS/red-team.md | 104 +++++ .agent/src/COMMANDS/risk-register.md | 80 ++++ .agent/src/GUIDE.md | 205 +++++++++ .agent/src/PROTOCOLS/00_INIT.md | 128 ++++++ .agent/src/PROTOCOLS/01_ANALYSE.md | 95 +++++ .agent/src/PROTOCOLS/02_ROADMAP.md | 106 +++++ .agent/src/PROTOCOLS/03_DESIGN.md | 142 +++++++ .agent/src/PROTOCOLS/04_DECOMPOSITION.md | 117 ++++++ .agent/src/PROTOCOLS/05_EXECUTION.md | 136 ++++++ .agent/src/PROTOCOLS/06_METASTATE.md | 145 +++++++ .agent/src/PROTOCOLS/07_HANDOFF.md | 113 +++++ .agent/src/TEMPLATES/adr-NNNN.md | 23 + .agent/src/TEMPLATES/analysis-report.md | 86 ++++ .agent/src/TEMPLATES/design-report.md | 80 ++++ .agent/src/TEMPLATES/handoff-summary.md | 57 +++ .agent/src/TEMPLATES/project-rules.md | 17 + .agent/src/TEMPLATES/project-state.md | 43 ++ .agent/src/TEMPLATES/request.json | 29 ++ .agent/src/TEMPLATES/risk-register.md | 7 + .agent/src/TEMPLATES/roadmap-sources.md | 42 ++ .../TEMPLATES/schemas/checkpoints-schema.json | 66 +++ .../schemas/decisions-index-schema.json | 60 +++ .../schemas/task-manifest-schema.json | 95 +++++ .agent/src/TEMPLATES/session-summary.md | 50 +++ .agent/src/TEMPLATES/task-manifest.json | 24 ++ .agent/src/TEMPLATES/task-manifest.md | 42 ++ .agent/src/VERSION | 1 + .agent/src/WORKFLOW.md | 240 +++++++++++ .agent/src/install.ps1 | 365 ++++++++++++++++ .agent/src/install.sh | 312 ++++++++++++++ .agent/tasks/manifest.json | 272 ++++++++++++ .agent/tasks/manifest.md | 86 ++++ .gitignore | 3 +- AGENTS.md | 155 +++---- docs/arch/map.md | 396 ------------------ docs/arch/todo.md | 316 -------------- 48 files changed, 4389 insertions(+), 916 deletions(-) create mode 100644 .agent/checkpoints.json create mode 100644 .agent/context/analysis-report.md create mode 100644 .agent/context/project-state.md create mode 100644 .agent/decisions/0001-sops-secrets-paths.md create mode 100644 .agent/decisions/index.json rename docs/arch/invariants.md => .agent/roadmap/sources.md (53%) create mode 100644 .agent/rules/project-rules.md create mode 100644 .agent/src/BOUNDARIES.md create mode 100644 .agent/src/CHANGELOG.md create mode 100644 .agent/src/COMMANDS/adr.md create mode 100644 .agent/src/COMMANDS/alt-arch.md create mode 100644 .agent/src/COMMANDS/invariant-tests.md create mode 100644 .agent/src/COMMANDS/red-team.md create mode 100644 .agent/src/COMMANDS/risk-register.md create mode 100644 .agent/src/GUIDE.md create mode 100644 .agent/src/PROTOCOLS/00_INIT.md create mode 100644 .agent/src/PROTOCOLS/01_ANALYSE.md create mode 100644 .agent/src/PROTOCOLS/02_ROADMAP.md create mode 100644 .agent/src/PROTOCOLS/03_DESIGN.md create mode 100644 .agent/src/PROTOCOLS/04_DECOMPOSITION.md create mode 100644 .agent/src/PROTOCOLS/05_EXECUTION.md create mode 100644 .agent/src/PROTOCOLS/06_METASTATE.md create mode 100644 .agent/src/PROTOCOLS/07_HANDOFF.md create mode 100644 .agent/src/TEMPLATES/adr-NNNN.md create mode 100644 .agent/src/TEMPLATES/analysis-report.md create mode 100644 .agent/src/TEMPLATES/design-report.md create mode 100644 .agent/src/TEMPLATES/handoff-summary.md create mode 100644 .agent/src/TEMPLATES/project-rules.md create mode 100644 .agent/src/TEMPLATES/project-state.md create mode 100644 .agent/src/TEMPLATES/request.json create mode 100644 .agent/src/TEMPLATES/risk-register.md create mode 100644 .agent/src/TEMPLATES/roadmap-sources.md create mode 100644 .agent/src/TEMPLATES/schemas/checkpoints-schema.json create mode 100644 .agent/src/TEMPLATES/schemas/decisions-index-schema.json create mode 100644 .agent/src/TEMPLATES/schemas/task-manifest-schema.json create mode 100644 .agent/src/TEMPLATES/session-summary.md create mode 100644 .agent/src/TEMPLATES/task-manifest.json create mode 100644 .agent/src/TEMPLATES/task-manifest.md create mode 100644 .agent/src/VERSION create mode 100644 .agent/src/WORKFLOW.md create mode 100644 .agent/src/install.ps1 create mode 100644 .agent/src/install.sh create mode 100644 .agent/tasks/manifest.json create mode 100644 .agent/tasks/manifest.md delete mode 100644 docs/arch/map.md delete mode 100644 docs/arch/todo.md diff --git a/.agent/checkpoints.json b/.agent/checkpoints.json new file mode 100644 index 0000000..ef6c32f --- /dev/null +++ b/.agent/checkpoints.json @@ -0,0 +1,36 @@ +{ + "metaagent_version": "3.0.0", + "session_id": "metaagent-init-2026-10-09", + "target_repo": "S:/Git/nixos", + "goal": "Установить metaagent, перенести накопленные данные (AGENTS.md, docs/arch/*) в структуру .agent/.", + "project_type": "existing", + "phases": { + "init": "completed", + "analyse": "completed", + "roadmap": "completed", + "design": "skipped", + "decomposition": "completed", + "execution": "in_progress", + "metastate": "pending", + "handoff": "pending" + }, + "tasks": [ + { "id": "T1", "title": "A1: mobile.nix импортирует несуществующий lib/xlib.nix", "status": "pending", "origin": "user:direct" }, + { "id": "T2", "title": "A2: убедиться, что nix flake check вообще запускается", "status": "pending", "origin": "user:direct" }, + { "id": "T3", "title": "A3: явная финальная политика nftables на VDS", "status": "pending", "origin": "user:direct" }, + { "id": "T4", "title": "B1: guard на несмонтированный носитель /mnt/services", "status": "pending", "origin": "user:direct" }, + { "id": "T5", "title": "B2: зафиксировать, что бэкапов в конфигурации нет", "status": "pending", "origin": "user:direct" }, + { "id": "T6", "title": "C1: вернуть расследование 3x-ui, потерянное при откате", "status": "pending", "origin": "user:direct" }, + { "id": "T7", "title": "C2: зафиксировать фактические версии панели и ядра 3x-ui", "status": "pending", "origin": "user:direct" }, + { "id": "T8", "title": "C3: убрать сервис автообновления 3x-ui", "status": "pending", "origin": "user:direct" }, + { "id": "T9", "title": "C4: записать, что ядро Xray — состояние панели, а не Nix", "status": "pending", "origin": "user:direct" }, + { "id": "T10", "title": "C5: решить судьбу reality443Forwarding", "status": "pending", "origin": "user:direct" }, + { "id": "T11", "title": "D1: пробросы роутера — главный недостающий инвариант", "status": "pending", "origin": "user:direct" }, + { "id": "T12", "title": "D2: зафиксировать 100.64.0.0 как Tailscale-адрес sapphira", "status": "pending", "origin": "user:direct" }, + { "id": "T13", "title": "D3: убрать мёртвое правило firewall на sapphira", "status": "pending", "origin": "user:direct" }, + { "id": "T14", "title": "E1: написать AGENTS.md в корне (с metaagent-шапкой)", "status": "completed", "origin": "user:direct" }, + { "id": "T15", "title": "E2: выбрать проверки, которые заменят половину инвариантов", "status": "pending", "origin": "user:direct" }, + { "id": "T16", "title": "E3: судьба 15 закомментированных модулей", "status": "pending", "origin": "user:direct" } + ], + "last_updated": "2026-10-09T20:30" +} diff --git a/.agent/context/analysis-report.md b/.agent/context/analysis-report.md new file mode 100644 index 0000000..5e4450d --- /dev/null +++ b/.agent/context/analysis-report.md @@ -0,0 +1,119 @@ +# Analysis Report + +## Session + +- **Session ID:** `metaagent-init-2026-10-09` +- **Target repo:** `S:/Git/nixos` +- **Date:** 2026-10-09 +- **Project type:** `existing` (NixOS-конфиг, 120 `.nix`, ~8.5k строк) + +## 1. Общая информация + +- **README:** одна строка без смысла (`"I'm a super newbie who just posted my stuff here. Now maybe about intermediate"`); функциональную роль README играет `AGENTS.md` (корень). +- **Лицензия:** не указана. +- **CI/CD:** отсутствует. `nix flake check` — единственная автоматическая защита, прогоняется вручную. `deploy/default.nix:27-29` — `checks = builtins.mapAttrs (... deployChecks)`, но они покрывают только deploy-сценарий. +- **Точка входа:** `flake.nix` → `nixosConfigurations.` (хосты) + `nixOnDroidConfigurations.` (Android). Реестр хостов: `configurations/default.nix:13-39`. +- **Система сборки:** Nix + NixOS flakes. Home-manager, sops-nix, disko, deploy-rs, grub2-themes, justray. + +## 2. Стек технологий (existing) + +| Компонент | Значение | +|---|---| +| Язык | Nix (`.nix`) | +| Фреймворк | NixOS modules + home-manager | +| База данных | PostgreSQL (в `modules/server/postgresql.nix`), sqlite (3x-ui) | +| Тестовый раннер | отсутствует (см. §5) | +| Пакетный менеджер | Nix (`nix flake`, `nix-env`, `nix profile`) | +| Линтер/форматтер | отсутствует (комментарий-density 38/120 файлов без комментариев — открытый вопрос 0.2) | + +## 3. Архитектура (existing) + +``` +flake.nix +├── configurations/ ← реестр хостов (1 запись = 1 машина) +│ ├── default.nix ← hosts + xlib + mkSystem +│ ├── .nix ← модульное тело хоста +│ └── hardware/.nix +├── home/ ← home-manager (per device-type) +├── modules/ +│ ├── options.nix ← кросс-модульные опции +│ ├── default.nix ← defaultModule + strictModule (для nix-on-droid) +│ ├── essentials/ ← packages, services, settings, ssh, shell, systemd-routines +│ └── / ← per-type: desktop/, server/, vds/, wsl/, containers/, termux/, other/ +├── lib/ +│ ├── mkSystem.nix ← nixosSystem + specialArgs(xlib, inputs) +│ └── xlib/ ← чистые данные: devices, dirs, helpers +├── overlays/, pkgs/, deploy/, secrets/ (sops) +└── .sops.yaml ← один age-ключ на secrets/.(yaml|json|env|ini) +``` + +**Паттерн:** модульный монолит с xlib-инъекцией (аналог dependency injection через `specialArgs`). + +**Ключевые модули:** + +| Модуль | Описание | +|---|---| +| `configurations/default.nix:13-39` | Реестр хостов (7 entries); `mkXlib` в строке 50 | +| `configurations/.nix` | Per-host: имя, тип, импорт модулей; 7 файлов | +| `lib/xlib/default.nix:38-77` | `mkXlib` — единственная точка сборки xlib | +| `lib/xlib/device.nix:12-41` | Закрытое множество device.types: { minimal, primary, secondary, server, vds, wsl, termux } | +| `lib/xlib/helpers.nix` | `mkBindMount`, `mkSystemdBind`, `mkServiceStorage`, `mkNtfsMount`, `mkExfatMount`, `mkTmpDirs`, `mkSymlinks` | +| `modules/options.nix` | Кросс-модульные опции: `host.builder.*`, `host."3x-ui".*` | +| `modules/default.nix:9-39` | `nixosModules.default` — импортируется на каждый NixOS-хост; `nixosModules.strict:40-53` — для nix-on-droid | +| `modules/essentials/` | `packages`, `services`, `settings`, `ssh`, `shell`, `systemd-routines` | +| `modules/users.nix` | Пользователь `oqyude` (uid 1000, sapphira=1001), sops-секреты, hostKey bootstrap | +| `modules/server/` | 20+ системных сервисов sapphira (см. `server/default.nix:imports`) | +| `modules/server/systemd.nix` | rsync oneshots с `requiresMountsFor` guard (единственный пример guard'а) | +| `modules/server/{nginx,coredns}.nix` | Reverse proxy + DNS для зон `zeroq.su` и `home.arpa` | +| `modules/containers/` | podman-контейнеры: 3x-ui (заморожен), tape-rotation, remnanode, kokoro-tts, openhands, remnawave-examples | +| `home/.nix` | Home-manager per device-type; для `root` — без профиля | +| `home/modules/opencode.nix` | OpenCode CLI + systemd user services (см. R2 — `Service` vs `serviceConfig`) | +| `deploy/default.nix` | deploy-rs ноды: sapphira, otreca, rydiwo (НЕ atoridu/wsl/epral) | + +## 4. Конвенции (existing) + +- **Стиль:** Nix-форматирование, разные отступы в разных файлах (нет единого стандарта). +- **Импорты:** `imports = [ ./foo.nix ./bar.nix ];` или list-spread. `lib.optional` для условных импортов. +- **Типизация:** types из `lib.types` (например, `lib.types.str`, `lib.types.bool`, `lib.types.attrsOf`). +- **Обработка ошибок:** `throw` + literal-сообщения (например, `lib/xlib/device.nix:12-41` — throw со списком валидных типов). +- **Логирование:** не формализовано; rsync oneshots в `modules/server/systemd.nix` — единственный пример с `-v` + structured output. + +## 5. Тесты (existing) + +- **Команда запуска:** отсутствует. Единственная полуавтоматическая проверка — `nix flake check`. +- **Всего тестов:** 0 +- **Пройдено:** N/A +- **Упало:** N/A +- **Пропущено:** N/A +- **Упавшие тесты:** N/A + +Кандидаты на CI-проверки (открытый вопрос 11.2): + +1. ни одного `:latest` в образах (grep по `image =`) +2. `nix flake check` зелёный — уже ловит A1 +3. домены в `coredns.nix` ↔ vhost'ы в `nginx.nix` совпадают в обе стороны +4. для каждого потребителя `mkServiceStorage` каталог существует на `External` +5. последнее правило самописной nftables-цепочки явное +6. `listen.addr` — адрес интерфейса, а не сеть +7. все файлы в `secrets/` матчат `path_regex` из `.sops.yaml` + +## 6. Базовая проверка (existing) + +- **Сборка:** не проверена в этой сессии (нет `nix` в PATH, AGENTS.md ссылается на `nix flake check`). +- **Запуск:** N/A (NixOS-конфиг, не приложение). +- **Git status:** рабочее дерево было чистым после коммита `7c9aa24` (yaml frontmatter в AGENTS.md). 1 modified файл (AGENTS.md) — fixed. + +## 7. Требования (N/A для existing) + +## 8. Примечания + +- **Проход по репозиторию 2026-10-05**: 120 `.nix`, ~8.5k строк. Подтверждённые + инварианты (1–6) зафиксированы в `.agent/rules/project-rules.md` (R1). +- **Слои 9–11** (home-manager, deploy, формат) перенесены в `.agent/roadmap/sources.md` + как открытые вопросы; см. §9.2, §10.1-10.4, §11.1-11.2 исходного `invariants.md`. +- **Шаблон инварианта** (4 оси: Утверждение, Где, Почему, Действие) — для новых + записей. Обратное: до правки `authelia.nix:51,108,109` шёл через хелпер + `sopsPath = name: "/run/secrets/${name}"`; `open-webui.nix:95`, `tape-rotation.nix:61`, + `remnawave.nix:61, 129, 136` — литеральный хардкод. См. ADR-0001. +- **Bootstrap-цикл ключа** (R3) — должен быть задокументирован, иначе при + переустановке хоста агент не выведет. Открытый вопрос 4.2. diff --git a/.agent/context/project-state.md b/.agent/context/project-state.md new file mode 100644 index 0000000..d68ee4d --- /dev/null +++ b/.agent/context/project-state.md @@ -0,0 +1,107 @@ +# Project State +# Auto-generated — updated by ANALYSE (initial) and METASTATE (on updates) + +**Last updated:** 2026-10-09T20:30 +**Session:** metaagent-init-2026-10-09 + +## Project Type + +`existing` — NixOS-конфиг домашнего флота. 7 host-outputs (6 NixOS + 1 nix-on-droid). + +## Tech Stack + +| Category | Technology | +|----------|-----------| +| Language | Nix | +| Framework | NixOS modules + home-manager | +| Database | PostgreSQL (sapphira), sqlite (3x-ui) | +| Test runner | none — `nix flake check` is the only guard | +| Package manager | Nix (flakes) | +| Linter/formatter | none | + +## Current Architecture + +Модульный монолит с xlib-инъекцией. `flake.nix` → `nixosConfigurations.` через `configurations/default.nix`. Каждый хост получает `xlib` (identity + dirs + helpers) через `specialArgs` в `lib/mkSystem.nix`. `modules/default.nix` импортирует `nixosModules.default` (все NixOS) или `nixosModules.strict` (только nix-on-droid). + +``` +configurations/ → 7 хостов (default, atoridu, rydiwo, otreca, sapphira, wsl, epral) +modules/ → essentials + per-type (desktop/server/vds/wsl/containers/termux) +home/ → home-manager per device-type +lib/xlib/ → чистые данные (devices, dirs, helpers) +deploy/ → deploy-rs ноды: sapphira, otreca, rydiwo +secrets/ → sops, один age-ключ +``` + +## Key Modules + +| Module | Status | Description | +|--------|--------|-------------| +| `configurations/default.nix` | existing | Реестр хостов + `mkXlib` | +| `lib/xlib/default.nix` | existing | `mkXlib` — единственная точка сборки xlib | +| `modules/default.nix` | existing | `defaultModule` (NixOS) + `strictModule` (nix-on-droid) | +| `modules/options.nix` | existing | Кросс-модульные опции | +| `modules/users.nix` | existing | Пользователь + sops + hostKey bootstrap | +| `modules/essentials/ssh.nix` | existing | openssh + hostKey | +| `modules/server/{nginx,coredns}.nix` | existing | Reverse proxy + DNS (`zeroq.su`, `home.arpa`) | +| `modules/server/postgresql.nix` | existing | PostgreSQL + `mkServiceStorage` (нужен B1 guard) | +| `modules/server/systemd.nix` | existing | rsync oneshots с `requiresMountsFor` | +| `modules/containers/3x-ui.nix` | frozen | Панель :latest, ядро Xray 26.7.x | +| `home/.nix` | existing | Per-type home-manager | +| `home/modules/opencode.nix` | existing | OpenCode CLI + systemd user (см. R2) | +| `deploy/default.nix` | existing | deploy-rs: sapphira, otreca, rydiwo | + +## Hosts + +| Attr | hostname | device.type | deploy | stateVersion | Примечание | +|---|---|---|---|---|---| +| `default` | nixos | minimal | — | — | Шаблон | +| `atoridu` | atoridu | primary | — (manual) | 26.05 | xanmod, mini-PC, без nixos-hardware | +| `rydiwo` | rydiwo | secondary | deploy-rs | 26.05 | Chuwi MiniBook, NTFS `lamet-drive` (mask=0000) | +| `otrecа` | otreca | vds | deploy-rs | 25.05 | VPS, Tailscale-only SSH, nftables (нужен A3) | +| `sapphira` | sapphira | server | deploy-rs | 25.05 | Домашний сервер, `firewall.enable = false` намеренно | +| `wsl` | wsl | wsl | — (manual) | 24.11 | WSL на vetymae (Windows 192.168.1.100) | +| `epral` | epral | termux | — | 24.05 | Android nix-on-droid, через `mobile.nix` | + +## Services (sapphira, активные) + +20+ системных сервисов + 6 контейнеров. Полный инвентарь в `tasks/manifest.json` (задачи A1-F). Все, использующие `mkServiceStorage`, нуждаются в B1 guard: postgresql, samba, homebox, gitea, navidrome, syncthing, uptime-kuma, immich, nextcloud, calibre-web, 3x-ui, tape-rotation. + +## Network + +``` +LAN 192.168.1.0/24: + .20 sapphira (зашит в ~30 мест; см. вопрос 6.6) + .1 роутер (gateway) + .100 vetymae (Windows-хост с WSL) + +Tailscale CGNAT 100.64.0.0/10: + 100.64.0.0 = sapphira (назначен вручную, в 4 файлах) + 100.86.62.4 = opencode на vetymae + 100.106.21.39 = miniflux + +Internet: + sapphira: пробросы роутера = 22, 80, 443, 8443 (xray), 22000 (syncthing) +``` + +## Decisions in Effect + +| ADR | Decision | Status | +|-----|----------|--------| +| ADR-0001 | sops-пути: `config.sops.secrets..path` (не литерал) | active | + +Подробнее: `.agent/decisions/0001-sops-secrets-paths.md`. + +## Testing Status + +Тестов нет. Единственная защита — `nix flake check`. Кандидаты на CI-проверки (7 штук) — в `analysis-report.md §5`. + +## Open Concerns + +- **B1**: нет guard'а на несмонтированный `/mnt/services` — сервисы стартуют на пустой БД. +- **A3**: nftables на VDS без явной финальной политики — неявный accept. +- **C1–C5**: 3x-ui заморожен, но без формального ADR; `podman.autoPrune` + `:latest` = деградация без коммита. +- **2.2/5.5**: `vetymae` / `lamet` / `therima` / `soptur` в `dirs.nix` — природа неясна. +- **4.1/4.2**: root-SSH и bootstrap-цикл ключа — не задокументированы. +- **6.6**: `192.168.1.20` зашит в 30 мест — рефакторинг отложен. + +Полный список — в `roadmap/sources.md` (открытые вопросы слоёв 0-11). diff --git a/.agent/decisions/0001-sops-secrets-paths.md b/.agent/decisions/0001-sops-secrets-paths.md new file mode 100644 index 0000000..2464faa --- /dev/null +++ b/.agent/decisions/0001-sops-secrets-paths.md @@ -0,0 +1,83 @@ +# ADR-0001: sops-пути — только через `config.sops.secrets..path` + +**Статус:** accepted + +**Дата:** 2026-10-09 + +**Контекст:** + +sops-nix материализует секреты на `/run/secrets/` по умолчанию. +Атрибут sops-блока (`config.sops.secrets.`) — единственный источник +истины для on-disk пути. Любой `path =` override на sops-блоке плюс хардкод +`"/run/secrets/"` в потребителе делает потребителя **молча** +сломанным: `nixos-rebuild` проходит, сервис стартует, файл читается — но +контент от прошлой версии или пустой. Симптом приходит из рантайма, не из CI. + +До этой правки (см. «Обратное») в кодовой базе были хардкоды путей в 5 +местах: `authelia.nix`, `open-webui.nix`, `tape-rotation.nix`, `remnawave.nix`. +В `remnawave.nix` тот же риск был двойной: путь хардкожен и в генераторе, +и в контейнере → расхождение двух копий = silent breakage. + +**Рассматриваемые альтернативы:** + +1. **A. `${config.sops.secrets..path}`** — единая точка истины. Любой + `path =` override автоматически подхватывается потребителем. +2. **B. Хелпер `sopsPath = name: "/run/secrets/${name}"`** — был в `authelia.nix:51` + до правки. Компактнее в написании, но тащит хардкод `/run/secrets/` в API + и делает невозможным `path =` override без правки потребителя. +3. **C. `let envFile = "/run/secrets/${name}"; in { … }` для композитных + случаев** — для `remnawave.nix`, где один и тот же env-файл читается и + генератором, и контейнером. Используется, но с явным комментарием. + +**Решение:** выбран вариант **A** для прямого доступа к одному секрету и +вариант **C** для композитных env-файлов в `remnawave.nix` (с комментарием). + +**Обоснование:** + +- **Единая точка истины.** Атрибут sops-блока — единственное место, где + определяется on-disk путь. Потребитель ссылается на `${config.sops.secrets..path}`. +- **Симметрия с `mkUserSecret`.** `users.nix:33-41` уже использует этот + паттерн (`config.sops.secrets..path`) — единый стиль по репо. +- **Без хелпера.** `sopsPath = name: "/run/secrets/${name}"` выглядит + компактнее, но скрывает хардкод `/run/secrets/`. Когда кто-то добавит + `path = "/var/lib/..."` в sops-блок, потребитель через хелпер молча + сломается. +- **Двухкопийный env-файл в remnawave.nix** — композитный случай, где + `let`-биндинг в одном scope с комментарием «не дублировать литерал» + делает связь явной. + +**Последствия:** + +- Позитивные: + - `nix flake check` (после T1, T2) ловит несоответствие путей в compile-time. + - `path =` override в sops-блоке не ломает потребителя молча. + - Единый стиль по репо: 5 мест исправлены, новые пишутся по образцу. +- Негативные: + - Длиннее в написании, чем `"/run/secrets/${name}"`. +- Риски: + - Если кто-то добавит нового потребителя sops и напишет литерал + `/run/secrets/` — молчаливое расхождение. Защита: код-ревью + + кандидат в CI-проверки (`secrets/missing-paths.nix` или grep). + +**Invariant:** + +> Любой потребитель sops-секрета в `modules/` ссылается на путь через +> `${config.sops.secrets..path}`, а не через литерал +> `"/run/secrets/"`. Атрибут sops-блока — единственный источник +> истины для on-disk пути. + +Зафиксировано в `.agent/rules/project-rules.md` (R1.7) + `analysis-report.md §8`. + +**Обратное (где было сломано до этой правки):** + +- `modules/server/authelia.nix:51,108,109` — через хелпер `sopsPath = name: "/run/secrets/${name}"`. +- `modules/containers/open-webui.nix:95` — литеральный хардкод. +- `modules/containers/tape-rotation.nix:61` — литеральный хардкод. +- `modules/containers/remnawave.nix:61, 129, 136` — литеральный хардкод, в двух местах (генератор + контейнер). + +**Затронутые файлы (после правки):** + +- `modules/server/authelia.nix:107-108` +- `modules/containers/open-webui.nix:101` +- `modules/containers/tape-rotation.nix:63` +- `modules/containers/remnawave.nix:16, 70, 138, 145` — `let`-биндинг для композитного env-файла (вариант C). diff --git a/.agent/decisions/index.json b/.agent/decisions/index.json new file mode 100644 index 0000000..7333a54 --- /dev/null +++ b/.agent/decisions/index.json @@ -0,0 +1,14 @@ +{ + "version": "3.0.0", + "updated_at": "2026-10-09T20:30", + "decisions": [ + { + "id": "0001", + "title": "sops-пути — только через config.sops.secrets..path", + "status": "accepted", + "date": "2026-10-09", + "file": ".agent/decisions/0001-sops-secrets-paths.md", + "tags": ["sops", "secrets", "security", "invariant"] + } + ] +} diff --git a/docs/arch/invariants.md b/.agent/roadmap/sources.md similarity index 53% rename from docs/arch/invariants.md rename to .agent/roadmap/sources.md index 4c613c1..12661b0 100644 --- a/docs/arch/invariants.md +++ b/.agent/roadmap/sources.md @@ -1,13 +1,9 @@ -# Инварианты: вопросы владельцу +# Roadmap Sources -Проход по репозиторию сверху вниз, 2026-10-05. 120 `.nix`, ~8.5k строк. - -Структура: -- Сводный ответ, ядро и ловушки → **`AGENTS.md`** (корень репозитория). -- Подробная карта архитектуры с per-host деталями и инвентарём сервисов → - **`docs/arch/map.md`**. -- Этот файл → **открытые вопросы** (слои 0–8) + ещё непрочитанные **слои 9–11** - (home-manager, deploy, формат). +Источники задач для фазы DECOMPOSITION. Собрано при проходе по репозиторию +2026-10-05, ответы владельца учтены. Полный Q&A-источник (с разделами «Вопрос», +«Факт», «Риск», «Кандидат») восстановим из git-истории: +`git log -p docs/arch/invariants.md | less` (последний коммит, где Q&A был полным). Пометки: `[!]` — найденный дефект, не вопрос. `[?]` — не смог определить по коду. `[✓]` — отвечено владельцем 2026-10-05. @@ -15,47 +11,33 @@ ## Статус ответов (2026-10-05) Отвечено: **2.1, 5.2, 6.1, 6.3, 6.5, 7.1, 7.2** (7 пунктов). Остальные ждут -ответа (таблица ниже). +ответа (таблица ниже). Ключевое из ответов: -Ключевое из ответов, что меняет картину: - -- **6.5 — моя ошибка.** `100.64.0.0` не «сетевой адрес вместо интерфейса»: - это Tailscale-адрес sapphira, назначенный вручную. -- **6.3 — это не дыра, а осознанное решение.** Граница держится на роутере: - на сервер пробрасываются ровно 5 портов — **443, 80, 22000 (syncthing), - 8443 (xray), 22 (ssh)**. `firewall.enable = false` на sapphira — следствие, - а не недосмотр. Проблема в другом: **список пробросов нигде не записан - в репозитории**, и именно его агент обязан уважать (D1 в `todo.md`). -- **7.2 — 3x-ui рабочий.** Откат сделан осознанно: панель последняя, ядро Xray - осталось на 26.7.28, миграция на 26.9 провалена, лишний код закомментирован. - Состояние — «заморожено», а не «сломано». -- **5.2 — подтверждённая дыра в защите данных.** Guard для несмонтированного - носителя не был продуман → задача B1. +- **6.5** — `100.64.0.0` не «сетевой адрес вместо интерфейса»: Tailscale-адрес + sapphira, назначен вручную. См. ADR R1.4. +- **6.3** — `firewall.enable = false` на sapphira не недосмотр: граница на + роутере (5 портов). См. ADR R1.3 + задачу D1. +- **7.2** — 3x-ui рабочий, откат осознанный. Состояние «заморожено», не сломано. + См. ADR R1.5 + задачи C1–C5. +- **5.2** — подтверждённая дыра в защите данных → задача B1. ## Сводка подтверждённых инвариантов -| # | Пункт | Краткая формулировка | См. | +См. `.agent/rules/project-rules.md` (R1.1–R1.7) — закреплено в `AGENTS.md` §«Подтверждённые инварианты» до мерджа. + +| # | Пункт | Краткая формулировка | Задача | |---|---|---|---| -| 1 | Все `outputs` флейка вычисляются | A1: правка `lib/xlib.nix` → `lib/xlib`; закрепить через `nix flake check` | todo A1 | -| 2 | External-диск монтируется до сервисов | mkServiceStorage + bind без guard'а → сервис стартует на пустой БД | todo B1 | -| 3 | Сетевая граница sapphira = роутер | 5 портов: 22, 80, 443, 8443, 22000; `firewall.enable = false` намеренно | todo D1 | -| 4 | `100.64.0.0` = Tailscale sapphira | Назначен вручную; в 4 файлах | AGENTS.md §5 | -| 5 | 3x-ui заморожен | Панель на latest; ядро Xray на 26.7.x; миграция 26.9 провалена | todo C1–C5 | -| 6 | nftables на VDS — явная финальная политика | Сейчас ruleset без финального правила + конфликт с `firewall.*` | todo A3 | -| 7 | sops-пути — через `config.sops.secrets..path` | Любой `path =` override на sops-блоке делает хардкод-потребителя молча сломанным: rebuild зелёный, сервис стартует, контент пустой | этот коммит, см. §S1 | +| 1 | Все `outputs` флейка вычисляются | A1: правка `lib/xlib.nix` → `lib/xlib`; закрепить через `nix flake check` | A1 | +| 2 | External-диск монтируется до сервисов | mkServiceStorage + bind без guard'а → сервис стартует на пустой БД | B1 | +| 3 | Сетевая граница sapphira = роутер | 5 портов: 22, 80, 443, 8443, 22000; `firewall.enable = false` намеренно | D1 | +| 4 | `100.64.0.0` = Tailscale sapphira | Назначен вручную; в 4 файлах | D2 | +| 5 | 3x-ui заморожен | Панель на latest; ядро Xray на 26.7.x; миграция 26.9 провалена | C1–C5 | +| 6 | nftables на VDS — явная финальная политика | Сейчас ruleset без финального правила + конфликт с `firewall.*` | A3 | +| 7 | sops-пути — через `config.sops.secrets..path` | Любой `path =` override на sops-блоке делает хардкод-потребителя молча сломанным | ADR-0001 | -## Сводка по ловушкам +## Открытые вопросы (слои 0–8) -Полная таблица (10 пунктов) в **`AGENTS.md`** → раздел «Ловушки». Кратко: -`firewall.enable=false` намеренно · uid=1001 на sapphira · `image = …:latest` -намеренно для 3x-ui · `reality443Forwarding=true` — следствие отката · -15 закомментированных модулей в server/default.nix · `serviceConfig` vs `Service` -в home-manager · nftables без финального правила · `100.64.0.0` не сеть · -`/mnt/services` mode 0777 · `stateVersion` разный между хостами. - -## Неотвеченные вопросы (слои 0–8) - -Самые важные — выделены. +Самые важные — выделены. Источник для новых задач в `.agent/tasks/manifest.json`. | ID | Вопрос | Что блокирует | |---|---|---| @@ -91,15 +73,6 @@ | 8.5 | Что слушает `:3002` (`/whiteboard` nextcloud)? | Карта сервисов | | 8.6 | Бэкапы вне Nix — записать | Документация | -## Где это раньше лежало - -До переноса в `AGENTS.md` / `map.md` здесь был подробный Q&A по слоям 0–8 -с разделами «Вопрос», «Факт», «Риск», «Кандидат». Этот текст сохранён в -git-истории файла (последний коммит, где Q&A был полным). Восстановить: -`git log -p docs/arch/invariants.md | less`. - ---- - ## Слой 9. home-manager **9.1** `home/home.nix:52-57` — для пользователя импортируется @@ -119,12 +92,7 @@ git-истории файла (последний коммит, где Q&A бы **9.3 [!]** `home/modules/opencode.nix:339-350` (`c73a698`): в home-manager нельзя писать `serviceConfig = { ... }` — рендерится литеральная секция `[serviceConfig]`, -которую systemd молча игнорирует («Unknown section 'serviceConfig'. Ignoring.»). -Правильно: `systemd.user.services.opencode-web.Service = { ... }`. -**Кандидат (готовый инвариант, стоит закрепить буквально в `AGENTS.md`):** -в home-manager cgroup-опции (`MemoryHigh`, `OOMScoreAdjust`, …) пишутся -в `systemd.user.services..Service`, **не** в `serviceConfig`. Ошибка -не диагностируется — она просто не применяется. +которую systemd молча игнорирует. Закреплено в R2. **9.4** `linger = true` добавлен ради `opencode-web` (`users.nix:71-75`) и включён **для всех** хостов. @@ -140,10 +108,7 @@ git-истории файла (последний коммит, где Q&A бы **9.6** Секреты opencode приходят в `~/.config/opencode/server.env` (dotenv), `~/.local/share/opencode/auth.json` и `account.json` (json, `key = ""`). **Кандидат:** эти три файла перезаписываются sops при каждой активации — ручные -правки в них теряются. Уже отражено в комментарии `users.nix:120-131`, стоит -закрепить как инвариант. - ---- +правки в них теряются. ## Слой 10. deploy и проверка @@ -152,94 +117,45 @@ git-истории файла (последний коммит, где Q&A бы **Вопрос:** почему не деплоится десктоп? И безопасно ли пересобирать ноутбук `rydiwo` по SSH (он может быть выключен/на другом Wi-Fi)? **Кандидат:** `deploy-rs` = только серверы + ноутбук; десктоп и WSL обновляются -вручную. Инвариант: не добавлять в `deploy.nodes` хост, который нельзя -пересобрать в любой момент без риска потерять доступ. +вручную. **10.2** `deploy/default.nix:18-19` — `sshUser = "oqyude"`, `user = "root"`. См. 4.1: root-доход по SSH не описан в конфигурации. -**Кандидат:** деплой требует ручной настройки root-доступа на каждом из 3 хостов — -это скрытая зависимость, которую агент не выведет. **10.3** `deploy/default.nix:27-29` — `checks = builtins.mapAttrs (... deployChecks)`. -**Вопрос:** `nix flake check` реально проходит сейчас? Учитывая 2.1 (`lib/xlib.nix`) -он должен падать на `nixOnDroidConfigurations`. Падает или `checks` покрывают -не всё дерево outputs? -**Кандидат (первое, что стоит сделать):** добиться, чтобы -`nix flake check` был зелёным — это единственная автоматическая защита от -подобных breakage'ов. +**Вопрос:** `nix flake check` реально проходит сейчас? **10.4** CI нет, `flake check` не запускается автоматически. **Кандидат:** минимальный локальный набор перед коммитом: `nix flake check && nix build .#nixosConfigurations.<хост>.config.system.build.toplevel --dry-run`. ---- - -## Слой 11. Формат (то, что я предлагаю зафиксировать как процесс) +## Слой 11. Формат **11.1** Где будет жить итог: `AGENTS.md` в корне (читается агентом всегда), `docs/arch/map.md` (карта хостов/сервисов), `docs/arch/invariants.md` (этот файл). -**Кандидат:** этот файл после ответов превращается в `docs/arch/invariants.md` -с колонкой «ответ» и становится источником для `AGENTS.md`; `AGENTS.md` — краткая -выжимка, без подробностей. +**Кандидат:** этот файл после ответов превращается в `.agent/roadmap/sources.md` +с колонкой «ответ» и становится источником для `.agent/context/project-state.md`; +`.agent/context/project-state.md` — сжатая выжимка, без подробностей. (Сделано.) **11.2** Какие инварианты можно превратить в автоматическую проверку (тогда они -перестанут «забываться»): -1. ни одного `:latest` в образах (grep по `image =`); -2. `nix flake check` зелёный; -3. каждый домен из `coredns.nix` имеет vhost в `nginx.nix` и наоборот; -4. каждый сервис в `mkServiceStorage` имеет каталог в `/mnt/services` на - `External`-диске; -5. в самописном nftables-ruleset последнее правило цепочки явное; -6. каждый `listen.addr` — реально назначенный адрес, а не сеть; -7. все файлы в `secrets/` матчат `path_regex` из `.sops.yaml`. - +перестанут «забываться»): 7 кандидатов в `analysis-report.md §5`. **Вопрос:** какие из этих проверок ты хочешь, а какие — лишний CI? ---- - ## Шаблон инварианта -Этот шаблон — для добавления новых инвариантов в этот документ -(и для зеркалирования в `AGENTS.md`). Та же 4-осевая структура -используется, чтобы вытащить «невидимое знание владельца» из -существующего кода в явное утверждение. +Этот шаблон — для добавления новых инвариантов в `.agent/rules/project-rules.md` +(и для зеркалирования в `AGENTS.md`). Та же 4-осевая структура используется, +чтобы вытащить «невидимое знание владельца» из существующего кода в явное +утверждение. 1. **Утверждение** — что именно верно и нельзя менять без осознанного решения. Один-два абзаца, никаких «может быть». 2. **Где** — конкретные файлы и строки. Агент не должен угадывать. 3. **Почему** — что происходит при нарушении. Лучше всего — сценарий (rebuild / рантайм), а не абстрактный риск. -4. **Действие** — `todo X.Y`, ссылка на коммит, или явное - «закреплено автоматической проверкой (см. §11.2)». +4. **Действие** — task id в `manifest.json`, ссылка на коммит, или явное + «закреплено автоматической проверкой (см. analysis-report.md §5)». Дополнительные поля по необходимости: «ловушка» (выглядит сломанным, -намеренно), «обратное» (где это уже было сломано раньше), -«как проверить» (grep / CI). - -### S1 — sops-пути: `config.sops.secrets..path` - -- **Утверждение.** Любой потребитель sops-секрета в `modules/` ссылается - на путь через `${config.sops.secrets..path}`, а не через - литерал `"/run/secrets/"`. Атрибут sops-блока — единственный - источник истины для on-disk пути. -- **Где.** `modules/server/authelia.nix:107-108`, - `modules/containers/open-webui.nix:101`, - `modules/containers/tape-rotation.nix:63` — потребители - sops-материализации. Отдельный случай — композитный env-файл, - **не** sops: `modules/containers/remnawave.nix:16, 70, 138, 145` - — там `envFile` в `let`-биндинге, чтобы две копии пути не - разъехались. -- **Почему.** sops-nix материализует секреты на `/run/secrets/` - по умолчанию, но `sops.secrets..path` это переопределяет. - Любой такой override в будущей правке делает хардкод-потребителя - **молча** сломанным: `nixos-rebuild` проходит, сервис стартует, - файл читается — но контент от прошлой версии или пустой. Симптом - приходит из рантайма, не из CI. В `remnawave.nix` тот же риск - был двойной: путь хардкожен и в генераторе, и в контейнере, и - расхождение двух копий → silent breakage. -- **Действие.** Закреплено в коммите этой правки. Автоматической - проверки пока нет (см. §11.2 — список потенциальных CI-проверок). -- **Обратное.** До правки: `authelia.nix:51,108,109` — через хелпер - `sopsPath = name: "/run/secrets/${name}"`; `open-webui.nix:95`, - `tape-rotation.nix:61`, `remnawave.nix:61, 129, 136` — литеральный - хардкод. \ No newline at end of file +намеренно), «обратное» (где это уже было сломано раньше), «как проверить» +(grep / CI). diff --git a/.agent/rules/project-rules.md b/.agent/rules/project-rules.md new file mode 100644 index 0000000..ab19125 --- /dev/null +++ b/.agent/rules/project-rules.md @@ -0,0 +1,160 @@ +# Project Rules + +Правила, которым агент обязан следовать во всех фазах. Источник: старый +`AGENTS.md` (накоплен при проходе по репозиторию 2026-10-05, ответы владельца +учтены 2026-10-05). Дополнения и уточнения — через `/adr`. + +## Обязательные правила + +### R1. Не ломать подтверждённые инварианты + +1. **Все `outputs` флейка должны вычисляться.** `configurations/mobile.nix:12` + импортировал несуществующий `lib/xlib.nix` — был сломан, `epral` не + собирался. Закреплено через `nix flake check`. +2. **Носитель данных (`/home/oqyude/External`) обязан быть смонтирован** до + старта `postgresql`, `n8n`, `samba`, `homebox`, `minecraft`, `3x-ui`, + `tape-rotation`. `mkServiceStorage` даёт `bind,x-systemd.automount,nofail` + — без guard'а сервис стартует на пустой БД. Задача `B1` в `manifest.json`. +3. **Сетевая граница sapphira — роутер.** `firewall.enable = false` намеренно. + Роутер пробрасывает ровно 5 портов: **443, 80, 22000 (syncthing), + 8443 (xray), 22 (ssh)**. `nginx.nix:225` (`allowedTCPPorts = [80 443]`) мёртв. + `openFirewall`/`allowedTCPPorts` на sapphira не имеют эффекта. +4. **`100.64.0.0` = Tailscale-адрес sapphira**, назначен вручную. Не сеть, не + ошибка. Используется в `nginx.nix`, `nextcloud.nix` (`trusted_proxies`), + `vds/systemd.nix`, `vds/nginx.nix`. При смене — править 4 файла. +5. **3x-ui заморожен.** Панель на последней версии (образ `:latest`), + ядро Xray на 26.7.x. Миграция на 26.9.x провалена. Обходные скрипты + (timer, migrateScript) отключены осознанно. **Не** обновлять ядро через + панель без записи в `decisions/` или `notes/`. +6. **nftables на VDS требует явной финальной политики.** Текущий ruleset + (`vds.nix:73-91`) — без явного последнего правила и без `policy` → неявный + accept. На otreca одновременно `nftables.enable = true` и `firewall.*` — + проверить, кто реально владеет ruleset'ом, перед правкой. +7. **sops-пути — через `config.sops.secrets..path`.** Любой + `path =` override на sops-блоке делает хардкод-потребителя молча + сломанным: rebuild зелёный, сервис стартует, контент пустой. См. ADR-0001. + +### R2. home-manager `Service` ≠ `serviceConfig` + +`home/modules/opencode.nix:339-350` (`c73a698`): в home-manager нельзя писать +`serviceConfig = { ... }` — рендерится литеральная секция `[serviceConfig]`, +которую systemd молча игнорирует («Unknown section 'serviceConfig'. Ignoring.»). +Правильно: `systemd.user.services.opencode-web.Service = { ... }`. В home-manager +cgroup-опции (`MemoryHigh`, `OOMScoreAdjust`, …) пишутся в +`systemd.user.services..Service`, **не** в `serviceConfig`. Ошибка +не диагностируется — она просто не применяется. + +### R3. Sops-цикл ключа задокументировать + +`/etc/ssh/id_ed25519` одновременно: `hostKeys` для sshd, `sops.age.sshKeyPaths` +для расшифровки, цель `ssh_key_private_known`, цель `ssh_key_public_host`. +Как разворачивается на чистой машине — **одноразовый bootstrap**. Должен быть +задокументирован, иначе при переустановке хоста агент не выведет. + +### R4. Перед деплоем External-диска — `findmnt` + +Перед рестартом сервисов, использующих `mkServiceStorage` (postgresql, samba, +homebox, gitea, navidrome, syncthing, uptime-kuma, immich, nextcloud, +calibre-web, 3x-ui, tape-rotation): + +```bash +findmnt /home/oqyude/External +findmnt /mnt/services +``` + +До реализации guard'а (задача B1) — это единственная защита от старта на +пустой БД. + +### R5. Проверка целостности sops-секретов + +Любая правка `users.nix` или потребителя sops-секрета требует: + +```bash +sops --version +nix build .#nixosConfigurations.<хост>.config.system.build.toplevel --dry-run +``` + +Расшифровка sops-секретов зависит от `/etc/ssh/id_ed25519` (см. R3). +Циклическая зависимость — см. «Где НЕ лезть без ответа». + +## Ловушки (выглядит сломанным, намеренно) + +Прежде чем чинить — проверить этот список. Здесь лежат решения, которые +иначе «поправляются» обратно и ломают рабочую систему. + +| Где | Что выглядит ошибкой | На самом деле | +|---|---|---| +| `server.nix:130` | `firewall.enable = false` при 20 сервисах на `0.0.0.0` | Роутер фильтрует, см. R1.3 | +| `mobile.nix:95`, `wsl.nix:59` | `stateVersion` 24.05 / 24.11 vs 26.05 | Каждый хост зафиксирован на своей версии | +| `users.nix:66` | `uid = if hostname == "sapphira" then 1001 else …` | Костыль под 1000 = удалённый `yuyus`; удалять только после миграции ФС | +| `3x-ui.nix:54` | `image = …:latest` | Панель намеренно latest; ядро Xray — на 26.7.x | +| `3x-ui.nix:33-35` | `reality443Forwarding = true` на VDS | Следствие отката `c8d4a12`; смысл утрачен, см. задачу C5 | +| `server/default.nix:33-47` | 15 закомментированных модулей | Отключены осознанно, см. задачу E3 | +| `opencode.nix:339` | `systemd.user.services.opencode-web.Service` | `serviceConfig` рендерится в секцию `[serviceConfig]`, systemd молча игнорирует (`c73a698`); см. R2 | +| `vds.nix:73-91` | nftables без финального правила | Известный пробел, см. задачу A3 | +| `100.64.0.0` | Первый адрес CGNAT `/10` | Tailscale-адрес sapphira, см. R1.4 | +| `server.nix:61-63` | `z /mnt/services 0777` | World-writable точка монтирования; см. задачу B1 | + +## Куда лезть по задаче + +| Задача | Файл | +|---|---| +| Добавить хост | `configurations/default.nix` + `configurations/.nix` + `configurations/{hardware,disko}/.nix` | +| Добавить системный сервис | `modules/server/.nix`, добавить в `modules/server/default.nix:imports` | +| Добавить home-пакет для пользователя | `home/.nix` (через `lib.mkIf` или просто список) | +| Добавить опцию, читаемую несколькими модулями | `modules/options.nix` | +| Изменить mount/имя пользователя | `lib/xlib/dirs.nix`, `lib/xlib/device.nix` | +| Изменить домен / сертификат | `modules/server/coredns.nix` + `modules/server/nginx.nix` (или `vds/`) | +| Sops-секрет | положить в `secrets/.`; `users.nix:99` уже подключает `secrets/default.yaml`; dotenv/json-секреты — через `mkUserSecret` | + +## Где НЕ лезть без ответа владельца + +- `secrets/` (sops-encrypted, расшифровываются `/etc/ssh/id_ed25519` → циклический bootstrap). +- `let deploy` без проверки deploy-rs нод: `rydiwo` (ноутбук, может быть выключен). +- Любая правка, противоречащая «Подтверждённым инвариантам» выше (R1). +- `vetymae` / `lamet` / `therima` / `soptur` в `dirs.nix` — природа неясна (открытый вопрос 2.2/5.5). +- `192.168.1.20` в 30 местах — менять только при готовности править все места (открытый вопрос 6.6). +- Ядро Xray 26.7.x → 26.9.x — миграция провалена, не повторять без отдельной задачи. + +## Проверки + +```bash +# все outputs вычисляются +nix flake check + +# правки применились на целевой хост +nix build .#nixosConfigurations..config.system.build.toplevel + +# nixOnDroid +nix build .#nixOnDroidConfigurations.epral.config.system.build.toplevel + +# внешний диск смонтирован (до рестарта сервисов на нём) +findmnt /home/oqyude/External +findmnt /mnt/services + +# state of guard-зависимостей (когда будет todo B1) +systemctl show postgresql -p Requires -p After | tr ' ' '\n' | grep -E 'mnt-|home-oqyude' + +# sops +sops --version +``` + +## Конвенции проекта + +- `xlib` (в `lib/xlib/`) — чистые данные: identity (`device`), capability flags, + директории, helper'ы. Передаётся в каждый модуль через `specialArgs`. + Конфиг не может переопределить `xlib` — единственная точка изменения это + `configurations/default.nix`. +- `device.type` ∈ { minimal, primary, secondary, server, vds, wsl, termux }. + `modules/defaultModule` импортирует `modules//` через + `lib.optional (!isDesktop && type != "minimal") (./. + "/${type}")`. +- `mkXlib` (`lib/xlib/default.nix:38-77`) — единственная точка сборки xlib. +- Опция живёт в `modules/options.nix`, если её **устанавливает** один модуль, + а **читает** другой. `host.reader.X.enable` живёт в `essentials/ssh.nix`, + потому что его объявляет и использует один модуль. +- `home/.nix` = единственный источник «что есть на этом хосте» для + пользователя; добавление пакета в новый тип = правильный файл, а не + `home/default.nix`. +- `.sops.yaml`: один age-ключ (`*default`), `path_regex: secrets/[^/]+\.(yaml|json|env|ini)$`. + Покрывает только плоские файлы в `secrets/` (без подкаталогов). Дополнительные + секреты dotenv/json — через `mkUserSecret` (`users.nix:33-41`). diff --git a/.agent/src/BOUNDARIES.md b/.agent/src/BOUNDARIES.md new file mode 100644 index 0000000..4549991 --- /dev/null +++ b/.agent/src/BOUNDARIES.md @@ -0,0 +1,45 @@ +# BOUNDARIES — Рамки и границы + +Что агенту **разрешено**, **запрещено** и в каких случаях **нужно остановиться**. + +## Разрешено + +| Действие | Примечание | +|---|---| +| Читать любые файлы в целевом репозитории | Включая `.git`, конфиги, историю | +| Создавать/изменять файлы в `.agent/` | Директория метаданных проекта (rules, decisions, tasks, context, requests, roadmap, archive) | +| Создавать `.temp/` в корне проекта | Для временных файлов агента. Всегда в `.gitignore` | +| Писать production-код | В фазе EXECUTION, по задачам из `manifest.json` | +| Рефакторить существующий код | Только если это часть задачи в `manifest.json` | +| Делать коммиты | По завершении задачи, перед созданием request | +| Создавать/дополнять `.gitignore` | Только для добавления `.temp/` | +| Устанавливать/обновлять зависимости | Через штатный пакетный менеджер проекта | +| Изменять конфигурационные файлы | Только если необходимо для сборки/тестов | +| Запускать сборку и тесты | Для верификации окружения и проверки request-ов | +| Читать документацию, issue, PRs | Для понимания контекста | +| Запрашивать уточнения у пользователя | Если не хватает информации для декомпозиции | +| Копировать исходники MetaAgent в `.agent/src/` целевого проекта | На фазе INIT, без перезаписи существующих файлов (если не указан `--update`) | +| Создавать/обновлять `AGENTS.md` в корне целевого проекта | Только если файла не существует | +| **Обязательно:** читать `.agent/rules/project-rules.md` перед каждой фазой | Правила пользователя имеют приоритет выше стандартных протоколов | +| Перемещать завершённые артефакты в `.agent/archive/` | На фазах METASTATE и HANDOFF | +| **Обязательно:** после выполнения задачи создавать request в `.agent/requests/active/` | Request — единица результата | +| Вызывать команды из `COMMANDS/` | По явной просьбе пользователя (`/adr`, `/red-team`, `/risk-register`, `/alt-arch`, `/invariant-tests`) | + +## Запрещено + +| Действие | Почему | +|---|---| +| Удалять файлы | Если файл мешает — сообщить пользователю | +| Менять удалённые настройки CI/CD | Если CI сломан — сообщить пользователю | +| Модифицировать код, не связанный с задачей | Только то, что нужно в рамках задачи из `manifest.json` | +| Выполнять команды (`/adr`, `/red-team`, и т.д.) без явной просьбы | Команды — on-demand, не авто-фаза | +| Задавать пользователю вопросы про depth / scale / фичи | В v3.0 нет шкалы глубины. Просто работай | + +## Когда остановиться + +1. **Репозиторий не собирается** — сообщить пользователю с логом ошибки, не продолжать. +2. **Неясна цель** — запросить уточнение, не гадать. +3. **Обнаружены секреты/токены** — не копировать, сообщить пользователю. +4. **Цель выходит за рамки одной сессии** — разбить, запросить приоритет. +5. **Проект не использует известные технологии** — запросить инструкцию по сборке. +6. **Непонятно, какую команду вызвать** — спросить пользователя, не угадывать. diff --git a/.agent/src/CHANGELOG.md b/.agent/src/CHANGELOG.md new file mode 100644 index 0000000..95c2009 --- /dev/null +++ b/.agent/src/CHANGELOG.md @@ -0,0 +1,87 @@ +# Changelog + +## 3.0.0 — Упрощение модели + +**Дата:** 2026-10-08 + +### Что изменилось + +Принята модель «жизненный цикл + команды на вызов» вместо «жизненный цикл с уровнями глубины». + +**Удалено:** +- Шкала глубины (depth 1-10) и все её варианты (Scaffold/Light/Standard/Deep/Maximum). +- Условные фичи в фазах: `adr`, `alternative_arch`, `red_team`, `risk_register`, `invariant_tests`. +- Интервью с пользователем на старте (5 вопросов про depth и фичи). +- `.agent/metaagent-request.md` — конфиг-файл, который сейчас не нужен. +- `TEMPLATES/metaagent-request.md`. + +**Добавлено:** +- Директория `COMMANDS/` с пятью on-demand инструкциями: `adr.md`, `red-team.md`, `risk-register.md`, `alt-arch.md`, `invariant-tests.md`. +- `GUIDE.md` — заменяет `META_AGENT_GUIDE.md`, описание цикла + список команд. +- `CHANGELOG.md` — этот файл. + +**Переименовано / перенумеровано:** +- `META_AGENT_GUIDE.md` → `GUIDE.md`. +- `PROTOCOLS/01_ANALYSIS.md` → `01_ANALYSE.md`. +- `PROTOCOLS/02_DESIGN.md` → `03_DESIGN.md`. +- `PROTOCOLS/03_DECOMPOSITION.md` → `04_DECOMPOSITION.md`. +- `PROTOCOLS/04_EXECUTION.md` → `05_EXECUTION.md`. +- `PROTOCOLS/05_HANDOFF.md` → `07_HANDOFF.md`. +- `PROTOCOLS/06_METASTATE.md` остался под тем же именем (теперь фаза 6). + +**Удалены протоколы:** +- `PROTOCOLS/00_CONFIG.md` — конфигурация больше не нужна. +- `PROTOCOLS/00_MIGRATE.md` — миграция теперь документируется в этом CHANGELOG. +- `PROTOCOLS/04_ENVIRONMENT_SETUP.md` — поглощён фазой `00_INIT.md`. +- `PROTOCOLS/02b_REDTEAM.md` — теперь команда `COMMANDS/red-team.md`. + +**Структура `.agent/checkpoints.json`** упрощена: убраны `config.depth`, `config.design.adr`, `config.red_team`, `config.risk_register`, `config.decomposition.invariant_tests`. + +### Миграция с v2.1 → v3.0 + +Для проектов, созданных с MetaAgent v2.1: + +1. **Удалить** из `.agent/checkpoints.json` секцию `config` целиком (она больше не читается). +2. **Удалить** `.agent/metaagent-request.md` (не используется). +3. **Удалить** `.agent/decisions/config.json`, если есть (аналог config для решений). +4. **Запустить** `install.sh --update` (или `install.ps1 -Update` / `install.bat --update`) — перезапишет исходники MetaAgent. +5. **Переименовать** пути в существующих артефактах: `layer-1/adr/` → `decisions/` (если остались с v1.x), `layer-2/analysis-report.md` → `context/analysis-report.md` и т.п. — это касается только проектов, оставшихся на v1.x. +6. **Записать** в `.agent/checkpoints.json` новое значение `metaagent_version: "3.0.0"`. + +`request.json`, `manifest.json`, `decisions/index.json` остаются в том же формате, что в v2.1. + +### Экономия + +| | v2.1 | v3.0 | +|---|---|---| +| Markdown строк всего | ~3 820 | ~1 800 (целевой) | +| Протоколов | 10 | 8 | +| Уровней конфигурации | 5 (depth) | 0 | + +--- + +## 2.1.0 — Project Loop + Work Loop + Requests + +**Дата:** 2025-08 (предыдущая версия) + +- Введён двухконтурный жизненный цикл: Project Loop (однократно) + Work Loop (циклически). +- Добавлены фазы: ROADMAP, METASTATE, RED_TEAM. +- Введены `requests/` как единица результата выполненной задачи. +- Введён `metaagent-request.md` с конфигом сессии (depth scale, фичи). +- Введена структура `.agent/` с семантическими директориями: `decisions/`, `tasks/`, `context/`, `rules/`, `requests/`, `roadmap/`, `archive/`. +- Шкала глубины 1-10 с условными фичами (adr, alternative_arch, red_team, risk_register, invariant_tests). + +## 2.0.0 — Реструктуризация `.agent/` + +- Переход от слоистой структуры `layer-0..3` к семантическим директориям. +- Полный MIGRATE-протокол для апгрейда с v1.x. + +## 1.1.0 — Добавлены rules, archive + +- `PROTOCOLS/01_ANALYSIS.md` обзавёлся правилами из `.agent/rules/`. +- Добавлена директория `archive/`. + +## 1.0.0 — Первый релиз + +- Односессионный pipeline: INIT → ANALYSE → DECOMP → SETUP → HANDOFF. +- Структура `layer-0..3`. diff --git a/.agent/src/COMMANDS/adr.md b/.agent/src/COMMANDS/adr.md new file mode 100644 index 0000000..e16f6cb --- /dev/null +++ b/.agent/src/COMMANDS/adr.md @@ -0,0 +1,95 @@ +# ADR — Architecture Decision Record + +## Назначение + +Зафиксировать архитектурное решение в `.agent/decisions/NNN-slug.md` так, чтобы будущий агент (или человек) мог понять: что решили, почему, какие альтернативы рассматривали, какие последствия. + +ADR создаются по явной команде пользователя: «запиши это как решение», «/adr», «сделай ADR для текущего подхода». + +## Когда вызывать + +- Принято неочевидное архитектурное решение (выбор БД, паттерна, библиотеки, структуры модулей). +- Решение может измениться в будущем — стоит зафиксировать контекст. +- Есть trade-off, который нужно объяснить следующему агенту. + +Не вызывать для очевидных вещей: «используем pytest», «классы называем в PascalCase». + +## Вход + +- Контекст решения: что обсуждалось, какие варианты сравнивались, что выбрали. +- `.agent/decisions/index.json` — текущий список ADR (для нумерации). +- `.agent/context/project-state.md` — текущее состояние проекта. + +## Шаги + +### 1. Определить номер + +Прочитать `.agent/decisions/index.json`. Следующий номер = max существующих + 1. Если файла нет — создать, начать с 001. + +### 2. Slug + +Короткое имя в kebab-case, отражающее суть: `use-sqlite-for-mvp`, `auth-via-jwt-cookies`, `modular-monolith`. + +### 3. Записать ADR + +Создать `.agent/decisions/{NNN}-{slug}.md` по шаблону `TEMPLATES/adr-NNNN.md`: + +```markdown +# {NNN}. {Заголовок} + +**Дата:** {YYYY-MM-DD} +**Статус:** Accepted | Superseded by {NNN} | Deprecated + +## Контекст + +{Что за проблема. Какие ограничения. Что нужно было решить.} + +## Решение + +{Что выбрали. Коротко и конкретно.} + +## Альтернативы, которые рассмотрели + +### {Альтернатива 1} +{Описание. Почему не выбрали.} + +### {Альтернатива 2} +{Описание. Почему не выбрали.} + +## Последствия + +### Положительные +- {что становится лучше} + +### Отрицательные +- {что становится хуже или сложнее} + +### Инварианты +- {что не должно сломаться, чтобы решение оставалось валидным} +``` + +### 4. Обновить index.json + +```json +{ + "version": "3.0.0", + "decisions": [ + { "id": "001", "title": "Использовать SQLite для MVP", "file": "001-use-sqlite-for-mvp.md", "status": "Accepted" } + ], + "last_updated": "{timestamp}" +} +``` + +### 5. Если есть supersession + +Если новый ADR отменяет старый — в старом ADR поставить `Статус: Superseded by {NNN}` и добавить ссылку. В новом — в контексте упомянуть, что отменяет. + +## Выход + +- `.agent/decisions/{NNN}-{slug}.md` +- Обновлённый `.agent/decisions/index.json` + +## Связанные команды + +- **/invariant-tests** — после ADR можно зафиксировать инварианты как задачи в manifest. +- **/alt-arch** — если хочется явно зафиксировать альтернативу до решения. diff --git a/.agent/src/COMMANDS/alt-arch.md b/.agent/src/COMMANDS/alt-arch.md new file mode 100644 index 0000000..a9b5c20 --- /dev/null +++ b/.agent/src/COMMANDS/alt-arch.md @@ -0,0 +1,85 @@ +# Alternative Architecture + +## Назначение + +Описать альтернативный вариант архитектуры / подхода, чтобы сравнить с текущим и принять осознанное решение. Не «сделать вместо», а «сравнить и выбрать». + +## Когда вызывать + +- Текущий дизайн кажется спорным, нужна трезвая оценка альтернативы. +- Хочется зафиксировать «почему не сделали иначе» — потом пригодится при росте. +- Перед крупным решением (выбор БД, монолит-vs-микросервисы, sync-vs-async). + +## Вход + +- Текущий дизайн / план (`.agent/context/design-report.md` или текущее состояние). +- Ограничения проекта (сроки, стек, бюджет). + +## Шаги + +### 1. Определить, что сравниваем + +Один конкретный вопрос: «SQLite vs PostgreSQL», «монолит vs микросервисы», «REST vs GraphQL», «sync-обработка vs очередь». + +### 2. Сформулировать альтернативу + +Краткое описание: что предлагается вместо текущего подхода. Без длинного дизайна — на уровне «как это работает и чем отличается». + +### 3. Сравнить + +| Аспект | Текущий | Альтернатива | +|---|---|---| +| Сложность реализации | | | +| Время до MVP | | | +| Производительность | | | +| Масштабирование | | | +| Поддерживаемость | | | +| Стоимость изменений | | | +| Риски | | | + +### 4. Записать + +Создать `.agent/context/alt-architecture.md` (если файла нет) или дополнить. Структура: + +```markdown +# Alternative Architecture — {что сравниваем} + +**Дата:** {YYYY-MM-DD} + +## Контекст + +{Почему рассматриваем альтернативу. Что не устраивает в текущем.} + +## Альтернатива + +{Краткое описание. Архитектура, ключевые компоненты, поток данных.} + +## Сравнение + +{Таблица из шага 3.} + +## Когда альтернатива выигрывает + +{В каких условиях стоит переключиться. Триггеры для миграции.} + +## Когда остаёмся на текущем + +{Что в текущем работает достаточно хорошо, чтобы не менять.} + +## Рекомендация + +{Остаёмся или мигрируем. Почему.} +``` + +### 5. Связать с ADR + +Если после сравнения принимается решение — использовать **/adr** для фиксации. Альтернативный файл остаётся как исторический артефакт. + +## Выход + +- `.agent/context/alt-architecture.md` + +## Связанные команды + +- **/adr** — зафиксировать итоговое решение. +- **/risk-register** — если альтернатива снимает/добавляет риски. diff --git a/.agent/src/COMMANDS/invariant-tests.md b/.agent/src/COMMANDS/invariant-tests.md new file mode 100644 index 0000000..0d975a8 --- /dev/null +++ b/.agent/src/COMMANDS/invariant-tests.md @@ -0,0 +1,68 @@ +# Invariant Tests + +## Назначение + +Превратить инварианты из ADR в задачи-тесты в `.agent/tasks/manifest.json`. Инвариант — это «что не должно сломаться, чтобы ADR оставался валидным». Без явного теста это просто слова. + +## Когда вызывать + +- После создания ADR, в секции «Инварианты» которого перечислены условия валидности решения. +- Когда хочется, чтобы архитектурные решения были защищены регрессионными тестами. + +## Вход + +- `.agent/decisions/*.md` — ADR с секцией «Инварианты». +- `.agent/tasks/manifest.json` — текущий манифест (для нумерации задач). + +## Шаги + +### 1. Найти ADR с инвариантами + +Прочитать все `.agent/decisions/*.md`, найти секции «Инварианты». + +### 2. Для каждого инварианта — задача + +Каждый инвариант = одна задача-тест. Формат: + +```json +{ + "id": "T-INV-001", + "title": "Invariant: auth-сессия не переживает рестарт сервиса", + "type": "test", + "origin": "invariant:001", + "depends_on": [], + "acceptance_criteria": [ + "Тест рестартит auth-сервис и проверяет, что все сессии инвалидированы", + "Тест проверяет, что refresh-токен не работает после рестарта" + ], + "files": [ + "tests/auth/test_invariants.py" + ] +} +``` + +### 3. Добавить в manifest + +Записать задачи в `.agent/tasks/manifest.json` с `status: "pending"`. Связать `depends_on` с задачами, которые реализуют компонент (если ещё не выполнены). + +### 4. Связать с ADR + +В самом ADR добавить (опционально) ссылку на задачу-инвариант: + +```markdown +## Инварианты +- {{ ... }} + +### Покрытие тестами +- T-INV-001: ... +``` + +## Выход + +- Новые задачи в `.agent/tasks/manifest.json` с `origin: "invariant:{adr_id}"` +- (опционально) обновлённый ADR со ссылкой на задачи + +## Связанные команды + +- **/adr** — источник инвариантов. +- **/risk-register** — некоторые инварианты рождаются из рисков. diff --git a/.agent/src/COMMANDS/red-team.md b/.agent/src/COMMANDS/red-team.md new file mode 100644 index 0000000..a43978c --- /dev/null +++ b/.agent/src/COMMANDS/red-team.md @@ -0,0 +1,104 @@ +# Red Team Review + +## Назначение + +Попытаться сломать текущий дизайн / архитектуру / план. Зафиксировать найденные уязвимости в `.agent/context/red-team-report.md`, чтобы разработчик мог их закрыть до реализации. + +Red Team — это adversarial-проход по дизайну. Не «улучшить», а «найти, что не так». + +## Когда вызывать + +- После фазы DESIGN, до декомпозиции задач. +- Когда дизайн кажется слишком гладким. +- Перед крупным рефакторингом. +- Когда непонятно, какие риски у текущего подхода. + +## Вход + +- `.agent/context/design-report.md` (если есть). +- `.agent/decisions/*.md` — связанные ADR. +- `.agent/context/project-state.md` — текущее состояние. + +## Шаги + +### 1. Прочитать целевой дизайн + +Понять, что именно ревьюится: вся архитектура, конкретный модуль, конкретное решение. + +### 2. Провести атаки по категориям + +#### 2.1. Нагрузка и масштабирование +- Что будет при 10x / 100x объёма? +- Где узкое место? +- Что сломается первым? + +#### 2.2. Отказы и доступность +- Что если упадёт БД / кэш / внешний сервис? +- Есть ли SPOF (single point of failure)? +- Как восстанавливаемся? + +#### 2.3. Безопасность +- Где хранятся секреты? +- Какие поверхности атаки? +- Что с аутентификацией / авторизацией? +- Injection, SSRF, XSS — что релевантно? + +#### 2.4. Корректность +- Где гонки (race conditions)? +- Что с консистентностью данных? +- Какие edge cases не покрыты? + +#### 2.5. Поддерживаемость +- Что будет сложно менять через год? +- Где связность, которую придётся разрывать? +- Какие зависимости могут устареть? + +#### 2.6. Миграция и совместимость +- Если меняем API — как старые клиенты переживут? +- Если меняем схему БД — что со старыми данными? +- Если выкатываем поэтапно — какой план? + +### 3. Записать отчёт + +Создать `.agent/context/red-team-report.md` (если файла нет) или дополнить: + +```markdown +# Red Team Review — {что ревьюим} + +**Дата:** {YYYY-MM-DD} +**Цель:** {что именно атакуем} + +## Критические находки + +### R1. {Краткое название} +- **Категория:** безопасность / нагрузка / корректность / ... +- **Сценарий:** {как воспроизвести} +- **Воздействие:** {что произойдёт} +- **Рекомендация:** {что сделать} + +## Существенные находки + +### R2. ... + +## Минорные находки + +### R3. ... + +## Что выдержало атаку + +- {Что оказалось надёжным — это тоже полезно знать.} +``` + +### 4. Связать с задачами + +Если находка превращается в задачу — добавить в `.agent/tasks/manifest.json` (фаза DECOMPOSITION) с `origin: "red-team:{номер_находки}"`. + +## Выход + +- `.agent/context/red-team-report.md` +- (опционально) новые задачи в manifest + +## Связанные команды + +- **/adr** — если Red Team выявил, что нужно зафиксировать решение иначе. +- **/risk-register** — для систематизации рисков. diff --git a/.agent/src/COMMANDS/risk-register.md b/.agent/src/COMMANDS/risk-register.md new file mode 100644 index 0000000..6b88d36 --- /dev/null +++ b/.agent/src/COMMANDS/risk-register.md @@ -0,0 +1,80 @@ +# Risk Register + +## Назначение + +Явный реестр допущений и рисков проекта в `.agent/context/risk-register.md`. Чтобы не держать в голове «ну мы же понимаем, что X может сломаться» — а записать, оценить и (если надо) превратить в задачи. + +## Когда вызывать + +- В начале проекта — зафиксировать стартовые допущения. +- При появлении нового риска (новый внешний сервис, новая зависимость, новое требование). +- При обзоре дизайна (после DESIGN или Red Team). + +## Вход + +- `.agent/context/design-report.md` (если есть). +- `.agent/context/analysis-report.md` — что уже знаем о проекте. +- `.agent/decisions/*.md` — принятые решения (могут быть источниками рисков). + +## Шаги + +### 1. Собрать риски + +Источники: +- Допущения, на которых держится дизайн («считаем, что PostgreSQL выдержит 1k qps»). +- Внешние зависимости без SLA. +- Технологии, которые команда не знает. +- Сроки, которые давят. +- Решения, которые сложно откатить. + +### 2. Оценить каждый риск + +По двум осям: +- **Вероятность** (1-низкая, 2-средняя, 3-высокая). +- **Воздействие** (1-небольшое, 2-серьёзное, 3-критическое). + +`score = вероятность × воздействие` (1-9). + +### 3. Записать + +Создать или дополнить `.agent/context/risk-register.md` по шаблону `TEMPLATES/risk-register.md`: + +```markdown +# Risk Register + +**Дата:** {YYYY-MM-DD} + +## Высокий риск (score 6-9) + +### R-001. {Краткое название} +- **Категория:** технический / продуктовый / организационный +- **Описание:** {что может пойти не так} +- **Воздействие:** {что будет если случится} +- **Вероятность:** 3 / 2 / 1 +- **Счёт:** 9 / 6 / 4 +- **Митигация:** {что делаем чтобы уменьшить} +- **Владелец:** {кто отвечает} +- **Статус:** open / mitigated / accepted / closed + +## Средний риск (score 3-4) +... + +## Низкий риск (score 1-2) +... + +## Закрытые риски +... +``` + +### 4. Связать с задачами + +Если риск требует действия — добавить задачу в `.agent/tasks/manifest.json` с `origin: "risk:R-001"`. + +## Выход + +- `.agent/context/risk-register.md` + +## Связанные команды + +- **/red-team** — источник технических рисков. +- **/adr** — некоторые риски закрываются через принятое решение. diff --git a/.agent/src/GUIDE.md b/.agent/src/GUIDE.md new file mode 100644 index 0000000..d331bab --- /dev/null +++ b/.agent/src/GUIDE.md @@ -0,0 +1,205 @@ +# MetaAgent GUIDE v3.0 + +MetaAgent — набор инструкций для AI-агента. Задача: превратить хаотичное общение с агентом в структурированный процесс, в котором состояние проекта переживает любую сессию. + +## Два слоя + +- **Цикл** (всегда, по необходимости) — последовательность фаз, которую агент проходит при работе с проектом. +- **Команды** (по запросу пользователя) — on-demand инструкции, которые не привязаны к фазе. + +Состояние проекта живёт в `.agent/` целевого репозитория. Следующий агент читает `.agent/` и не лезет в исходники. + +--- + +## Цикл + +``` + .agent/checkpoints.json + │ + ▼ + ┌─────────────────────────────────────┐ + │ PROJECT LOOP (разово) │ + │ │ + │ INIT → ANALYSE → ROADMAP → │ + │ → [DESIGN] → DECOMPOSITION │ + │ │ + │ Выход: .agent/tasks/manifest.json │ + └──────────────────┬──────────────────┘ + │ + ▼ + ┌─────────────────────────────────────┐ + │ WORK LOOP (циклически) │ + │ │ + │ EXECUTION → (request) → │ + │ → METASTATE (по команде) │ + │ │ + │ Беру задачу → делаю → request → │ + │ накопилось → METASTATE │ + └──────────────────┬──────────────────┘ + │ + ▼ + HANDOFF (завершение) +``` + +Фазы выполняются **строго последовательно** внутри PROJECT LOOP. WORK LOOP повторяется многократно. + +### Ветвление + +| Тип проекта | Цикл | +|---|---| +| **existing** | INIT → ANALYSE → ROADMAP → DECOMPOSITION → EXECUTION → METASTATE → HANDOFF | +| **greenfield / scaffold** | + фаза DESIGN между ROADMAP и DECOMPOSITION | + +Тип проекта определяется автоматически в фазе ANALYSE. Никакого интервью с пользователем, никакой шкалы глубины. + +--- + +## Фазы + +| # | Фаза | Протокол | Что делает | +|---|---|---|---| +| 0 | INIT | `PROTOCOLS/00_INIT.md` | Создаёт `.agent/`, ставит исходники, инициализирует checkpoints | +| 1 | ANALYSE | `PROTOCOLS/01_ANALYSE.md` | Сканирует проект, создаёт `analysis-report.md` + начальный `project-state.md` | +| 2 | ROADMAP | `PROTOCOLS/02_ROADMAP.md` | Собирает источники задач (FUTURE, ADR, user-запросы) → `roadmap/sources.md` | +| 3 | DESIGN | `PROTOCOLS/03_DESIGN.md` | Только greenfield. Архитектура, модули, API, модели | +| 4 | DECOMPOSITION | `PROTOCOLS/04_DECOMPOSITION.md` | Разбивает цель на атомарные задачи → `tasks/manifest.json` | +| 5 | EXECUTION | `PROTOCOLS/05_EXECUTION.md` | Цикл: берёт задачу → код → тесты → коммит → request | +| 6 | METASTATE | `PROTOCOLS/06_METASTATE.md` | По команде. Ревью requests, обновление project-state, handoff-summary | +| 7 | HANDOFF | `PROTOCOLS/07_HANDOFF.md` | Валидация `.agent/`, финализация checkpoints, session-summary | + +--- + +## Команды + +Эти инструкции выполняются **по явной просьбе пользователя** в любой момент сессии. Они не привязаны к фазе. + +| Команда | Файл | Что делает | +|---|---|---| +| «запиши ADR» / «/adr» | `COMMANDS/adr.md` | Создаёт `.agent/decisions/NNN-slug.md` | +| «red team» / «/red-team» | `COMMANDS/red-team.md` | Создаёт `.agent/context/red-team-report.md` — попытка сломать дизайн | +| «risk register» / «/risk-register» | `COMMANDS/risk-register.md` | Создаёт `.agent/context/risk-register.md` | +| «альтернативная архитектура» / «/alt-arch» | `COMMANDS/alt-arch.md` | Описывает альтернативу текущему дизайну | +| «invariant-тесты» / «/invariant-tests» | `COMMANDS/invariant-tests.md` | Создаёт задачи-инварианты для ADR | + +### Когда вызывать + +- **ADR** — после архитектурного решения, которое нужно зафиксировать. Типично во время DESIGN или при появлении неочевидного выбора в EXECUTION. +- **Red Team** — после готового дизайна, чтобы найти слабые места до реализации. +- **Risk Register** — в начале проекта или при появлении новых допущений. +- **Alt Arch** — если сомневаетесь в выбранном подходе, хотите сравнить варианты. +- **Invariant Tests** — после ADR, чтобы зафиксировать «что не должно сломаться». + +Команды **не обязательны**. Если не вызваны — не выполняются. Состояние проекта от них не зависит. + +--- + +## Структура `.agent/` + +``` +.agent/ + checkpoints.json # состояние сессии (ядро) + session-summary.md # краткая сводка сессии + handoff-summary.md # сводка для следующего агента (создаётся METASTATE) + + src/ # исходники MetaAgent (всегда) + GUIDE.md + BOUNDARIES.md + CHANGELOG.md + PROTOCOLS/ + COMMANDS/ + TEMPLATES/ + VERSION + install.sh / install.ps1 + + rules/ + project-rules.md # ваши правила — читать перед каждой фазой + + roadmap/ # источники задач + sources.md + archive/ + + decisions/ # ADR + index.json + 001-*.md + + tasks/ # задачи + manifest.json + manifest.md + backlog/ + + requests/ # результаты выполненных задач + active/ # ready_for_review + archive/ # approved / rejected + + context/ + analysis-report.md + project-state.md # обновляется в METASTATE + design-report.md # только greenfield + red-team-report.md # если вызывали /red-team + risk-register.md # если вызывали /risk-register + baseline-test-report.log + + archive/ + index.json + tasks/ + decisions/ + requests/ + checkpoints/ +``` + +`.temp/` в корне проекта — для временных файлов агента. Всегда в `.gitignore`. + +--- + +## Checkpoints + +`checkpoints.json` обновляется после каждой фазы: + +```json +{ + "metaagent_version": "3.0.0", + "session_id": "", + "target_repo": "", + "goal": "<цель>", + "project_type": "existing | greenfield | scaffold", + "phases": { + "init": "completed", + "analyse": "completed", + "roadmap": "completed", + "design": "skipped", + "decomposition": "completed", + "execution": "in_progress", + "metastate": "pending", + "handoff": "pending" + }, + "tasks": [ + { "id": "T1", "title": "...", "status": "in_progress", "origin": "user:direct" } + ], + "last_updated": "" +} +``` + +Секции `config` больше нет. Параметры, которые раньше были в `config` (depth, adr, red_team и т.п.), теперь либо не существуют, либо живут в отдельных командах. + +--- + +## Принципы + +### Цикл vs команды + +Цикл — это «что агент делает по умолчанию». Команды — «что агент делает по явной просьбе». Не путать: ADR не запускается автоматически в DESIGN, а только когда пользователь скажет «запиши это как решение». + +### `.agent/` как слепок проекта + +После METASTATE `.agent/` содержит всю картину. Следующий агент читает только `.agent/`, не исходники. + +### Request — единица результата + +Каждая выполненная задача в EXECUTION завершается созданием `request` (`.agent/requests/active/req-{id}.json`). Request содержит суть изменений, коммиты, верификацию, закрытые acceptance criteria. Ревью request-ов происходит в METASTATE. + +### Правила выше протоколов + +Перед каждой фазой читать `.agent/rules/project-rules.md`. Если правило пользователя противоречит протоколу — следовать правилу. + +### Контекст бесконечно не растёт + +Завершённые задачи архивируются в `.agent/archive/tasks/`, request-ы — в `.agent/requests/archive/`. Текущий manifest остаётся lean. diff --git a/.agent/src/PROTOCOLS/00_INIT.md b/.agent/src/PROTOCOLS/00_INIT.md new file mode 100644 index 0000000..eab0cd3 --- /dev/null +++ b/.agent/src/PROTOCOLS/00_INIT.md @@ -0,0 +1,128 @@ +# Протокол 00: Инициализация (INIT) + +## Цель + +Подготовить `.agent/` в целевом репозитории: установить исходники MetaAgent, создать структуру директорий, инициализировать `checkpoints.json`, создать/обновить `AGENTS.md`. + +INIT выполняется **один раз** в начале работы с проектом. Если `.agent/` уже существует и инициализирован — пропускается. + +## Вход + +- Целевой репозиторий (путь или текущая директория) +- `VERSION` — текущая версия MetaAgent +- Опционально: существующий `.agent/` (если обновление) + +## Шаги + +### 0.1. Определить целевой репозиторий + +Если не указан явно — текущая рабочая директория. Если указан как URL — клонировать во временную директорию, дальше работать с копией. + +### 0.2. Проверить существующий `.agent/` + +Если `.agent/` существует: + +- Прочитать `.agent/checkpoints.json` → `metaagent_version` +- Если `metaagent_version == VERSION` → INIT уже выполнен, выйти +- Если версия старше → запустить `install.sh --update` (Unix) или `install.ps1 -Update` (Windows) для переустановки исходников, затем выйти +- Если `.agent/` есть, но `checkpoints.json` отсутствует → продолжить INIT (создать checkpoints) + +Если `.agent/` не существует → продолжить INIT. + +### 0.3. Создать структуру `.agent/` + +Создать директории: + +``` +.agent/ + src/ # исходники MetaAgent (копируются из METAAGENT_SRC) + rules/ + decisions/ + tasks/ + backlog/ + context/ + requests/ + active/ + archive/ + roadmap/ + archive/ + archive/ + tasks/ + decisions/ + requests/ + checkpoints/ +``` + +### 0.4. Создать `.temp/` в корне проекта + +Если не существует — создать `.temp/` в корне целевого репозитория. Добавить в `.gitignore` (если его нет — создать с одной строкой `.temp/`). + +### 0.5. Скопировать исходники MetaAgent + +Скопировать в `.agent/src/`: + +- `GUIDE.md` +- `BOUNDARIES.md` +- `CHANGELOG.md` +- `VERSION` +- `PROTOCOLS/` +- `COMMANDS/` +- `TEMPLATES/` +- `install.sh`, `install.ps1` + +Существующие файлы в `.agent/src/` не перезаписывать (только с явным `--update`). + +### 0.6. Создать `.agent/rules/project-rules.md` + +Если файла нет — создать по шаблону `TEMPLATES/project-rules.md`. + +### 0.7. Создать/обновить `AGENTS.md` в корне + +Если `AGENTS.md` в корне проекта отсутствует — создать по `AGENTS.template.md` с подставленной версией. + +Если существует и не относится к MetaAgent — не трогать (попросить пользователя переименовать или подтвердить перезапись). + +### 0.8. Инициализировать `checkpoints.json` + +Создать `.agent/checkpoints.json`: + +```json +{ + "metaagent_version": "3.0.0", + "session_id": "", + "target_repo": "<путь>", + "goal": null, + "project_type": null, + "phases": { + "init": "completed", + "analyse": "pending", + "roadmap": "pending", + "design": "pending", + "decomposition": "pending", + "execution": "pending", + "metastate": "pending", + "handoff": "pending" + }, + "tasks": [], + "last_updated": "" +} +``` + +Поля `goal` и `project_type` остаются `null` до фазы ANALYSE (goal может быть задан пользователем заранее — тогда заполнить сразу). + +## Выход + +- `.agent/` с полной структурой +- `.agent/src/` с актуальными исходниками MetaAgent +- `.agent/rules/project-rules.md` +- `.agent/checkpoints.json` со `session_id` и `phases.init = "completed"` +- `AGENTS.md` в корне проекта +- `.temp/` в корне + `.gitignore` обновлён + +## Критерии завершения + +- [ ] `.agent/` содержит все обязательные директории +- [ ] `.agent/src/` содержит GUIDE.md, PROTOCOLS/, COMMANDS/, TEMPLATES/, VERSION +- [ ] `.agent/checkpoints.json` валиден (JSON parse) +- [ ] `AGENTS.md` присутствует в корне +- [ ] `.temp/` существует и в `.gitignore` diff --git a/.agent/src/PROTOCOLS/01_ANALYSE.md b/.agent/src/PROTOCOLS/01_ANALYSE.md new file mode 100644 index 0000000..7d0b997 --- /dev/null +++ b/.agent/src/PROTOCOLS/01_ANALYSE.md @@ -0,0 +1,95 @@ +# Протокол 01: Анализ репозитория (ANALYSE) + +## Цель + +Составить полную картину целевого репозитория: тип проекта, стек, архитектура, конвенции, состояние тестов. Создать начальный слепок проекта. + +## Вход + +- Целевой репозиторий +- `.agent/checkpoints.json` (фаза analyse: pending) +- `.agent/rules/project-rules.md` — прочитать первым + +## Шаги + +### 1.1. Прочитать правила проекта + +Прежде чем что-либо делать — прочитать `.agent/rules/project-rules.md`. Если есть правила, применить их к фазе. + +### 1.2. Определить тип проекта + +Просканировать корень репозитория: + +- **`existing`** — есть исходный код, тесты, система сборки (`.py`, `.js`, `.ts`, `.rs`, `.go` и т.д. помимо конфигов и README). +- **`greenfield`** — пусто или только README/LICENSE/.gitignore. +- **`scaffold`** — есть базовая структура (`pyproject.toml`/`package.json`), но нет значимого кода. + +Записать тип в `checkpoints.json → project_type`. + +### 1.3. Сканировать проект + +Для `existing` / `scaffold` собрать: + +- **README** — описание, инструкции по сборке/тестам. +- **Лицензия** — какой LICENSE. +- **CI/CD** — `.github/workflows/`, `.gitlab-ci.yml`, `Jenkinsfile`, `Makefile`. +- **Стек** — язык, фреймворк, БД, тестовый раннер, пакетный менеджер, линтер. +- **Структура** — `tree -L 3` (не более 3 уровней). +- **Архитектурный паттерн** — MVC, модульный монолит, микросервисы, слоистая. +- **Ключевые модули/пакеты** — список с краткой ответственностью. +- **Конвенции** — стиль, именование, обработка ошибок, логирование. +- **Тесты** — где лежат, как запускаются, текущее состояние (запустить). +- **Сборка** — выполняется ли проект. + +Для `greenfield` — извлечь требования из README: + +- Функциональные требования (user stories, сценарии). +- Нефункциональные (стек, производительность, безопасность). +- Бизнес-контекст (зачем, для кого). +- Сомнительные / неясные требования (вопросы пользователю). + +### 1.4. Создать analysis-report + +Записать `.agent/context/analysis-report.md` по шаблону `TEMPLATES/analysis-report.md`. Заполнить соответствующие секции. + +### 1.5. Создать начальный project-state + +Создать `.agent/context/project-state.md` по шаблону `TEMPLATES/project-state.md`. Это **начальный** слепок. В дальнейшем обновляется в фазе METASTATE. + +Заполнить: +- Тип проекта +- Краткая архитектура (из шага 1.3) +- Ключевые модули и их статус +- Tech stack +- Статус тестов + +### 1.6. Обновить checkpoints + +```json +{ + "phases": { "analyse": "completed" }, + "project_type": "existing | greenfield | scaffold", + "last_updated": "" +} +``` + +## Ветвление + +| project_type | Следующая фаза | +|---|---| +| `existing` | ROADMAP → DECOMPOSITION (DESIGN пропускается) | +| `greenfield` | ROADMAP → DESIGN → DECOMPOSITION | +| `scaffold` | ROADMAP → DESIGN → DECOMPOSITION | + +## Выход + +- `.agent/context/analysis-report.md` +- `.agent/context/project-state.md` (начальный) +- Обновлённый `checkpoints.json` + +## Критерии завершения + +- [ ] Тип проекта определён +- [ ] `analysis-report.md` содержит все соответствующие секции +- [ ] `project-state.md` создан с начальным слепком +- [ ] `checkpoints.json` обновлён diff --git a/.agent/src/PROTOCOLS/02_ROADMAP.md b/.agent/src/PROTOCOLS/02_ROADMAP.md new file mode 100644 index 0000000..c1eb407 --- /dev/null +++ b/.agent/src/PROTOCOLS/02_ROADMAP.md @@ -0,0 +1,106 @@ +# Протокол 02: Дорожная карта (ROADMAP) + +## Цель + +Собрать все источники задач для проекта, приоритизировать их и записать в `.agent/roadmap/sources.md`. ROADMAP — мост между видением проекта и конкретными задачами в манифесте. + +## Вход + +- `.agent/context/analysis-report.md` +- Цель сессии (goal из `checkpoints.json` или запрос пользователя) +- `FUTURE/` — директория долгосрочных планов (если существует) +- `.agent/decisions/index.json` — принятые ADR (опционально) +- `.agent/checkpoints.json` (фаза roadmap: pending) +- `.agent/rules/project-rules.md` — прочитать первым + +## Шаги + +### 2.1. Прочитать правила проекта + +Прочитать `.agent/rules/project-rules.md`, применить к фазе. + +### 2.2. Сканировать FUTURE/ + +Если в корне проекта существует `FUTURE/`: + +- Прочитать все `.md` файлы. +- Зафиксировать: название, статус (active/archived), приоритет, зависимости. +- Какие планы реализованы, какие ожидают. + +### 2.3. Сканировать ADR + +Если существует `.agent/decisions/index.json`: + +- Прочитать индекс ADR. +- Определить, какие решения требуют реализации (не все ADR технические). +- Для каждого — сформулировать задачу-кандидат. + +### 2.4. Собрать внешние источники + +- Запрос пользователя (goal). +- issues / feedback (если доступны). +- Tech debt, выявленный в ANALYSE. + +### 2.5. Приоритизировать + +Присвоить каждой задаче приоритет: + +| Приоритет | Описание | +|---|---| +| **P0** | Критично, делать следующим | +| **P1** | Важно, сделать скоро | +| **P2** | Желательно | +| **P3** | Долгосрочно / отложено | + +Правила: + +- Блокирующие зависимости поднимают приоритет. +- User-запросы получают P0-P1 по умолчанию. +- ADR-задачи получают приоритет по срочности решения. + +### 2.6. Создать sources.md + +Создать `.agent/roadmap/sources.md` по шаблону `TEMPLATES/roadmap-sources.md`: + +```markdown +# Roadmap Sources + +## FUTURE Plans +| План | Приоритет | Статус | +|------|-----------|--------| + +## ADR-Derived Tasks +| ADR | Задача | Приоритет | +|-----|--------|-----------| + +## User Requests +| Запрос | Приоритет | Источник | +|--------|-----------|----------| + +## Agent-Identified Improvements +| Наблюдение | Задача | Приоритет | +|------------|--------|-----------| + +## Consolidated Priority Queue +1. task (origin) — P0 +``` + +### 2.7. Архивация + +Если в `.agent/roadmap/archive/` есть предыдущие версии — оставить справочно. + +Если планы из `FUTURE/*` больше не актуальны — переместить в `FUTURE/archive/`. + +## Выход + +- `.agent/roadmap/sources.md` +- Возможно обновлённый `FUTURE/` +- `checkpoints.json: phases.roadmap = "completed"` + +## Критерии завершения + +- [ ] Все источники просканированы (FUTURE, ADR, user, agent) +- [ ] `sources.md` создан с приоритетами P0-P3 +- [ ] Каждая задача имеет origin-ссылку на источник +- [ ] Устаревшие планы перемещены в archive +- [ ] `checkpoints.json` обновлён diff --git a/.agent/src/PROTOCOLS/03_DESIGN.md b/.agent/src/PROTOCOLS/03_DESIGN.md new file mode 100644 index 0000000..531f4fa --- /dev/null +++ b/.agent/src/PROTOCOLS/03_DESIGN.md @@ -0,0 +1,142 @@ +# Протокол 03: Архитектурное проектирование (DESIGN) + +## Цель + +Спроектировать архитектуру, модули, данные и интерфейсы для greenfield/scaffold-проекта. + +DESIGN выполняется **только** для `project_type = greenfield` или `scaffold`. Для existing-проектов пропускается. + +## Вход + +- `.agent/context/analysis-report.md` (project_type: greenfield или scaffold) +- `.agent/roadmap/sources.md` (опционально) +- `.agent/checkpoints.json` (фаза design: pending) +- `.agent/rules/project-rules.md` — прочитать первым + +## Правила + +1. **Реалистичность** — архитектура реализуема за 1 сессию (до 10 задач). +2. **Документируемость** — каждый модуль, модель и интерфейс описывается в `design-report.md`. +3. **Тестируемость** — каждый компонент проектируется с учётом тестирования. +4. **Итеративность** — первая версия минимально рабочая (MVP), расширения — отдельными задачами. + +## Шаги + +### 3.1. Прочитать правила проекта + +Прочитать `.agent/rules/project-rules.md`, применить. + +### 3.2. Технологический стек + +Если стек не указан в README — предложить обоснованный выбор. Если указан — зафиксировать. + +Для каждого компонента: +- Язык и версия +- Фреймворк / библиотека +- База данных (движок, схема) +- Инфраструктура (Docker, CI/CD, хостинг) + +### 3.3. High-level архитектура + +- **Паттерн** — монолит, модульный монолит, микросервисы, слоистая, луковая. +- **Компоненты** — что делает каждый модуль/сервис. +- **Схема взаимодействия** — текстовое описание потоков данных. + +``` +[Client] → HTTP → [API Gateway] → [Auth Service] + ↓ + [Core Service] → [Database] + ↓ + [External API] → [3rd Party] +``` + +### 3.4. Модули + +| Поле | Описание | +|---|---| +| Имя модуля | `app/services/cashflow.py` | +| Ответственность | Что делает | +| Ключевые классы/функции | Сигнатуры без реализации | +| Зависимости | Какие модули нужны | +| Контракт | Что экспортирует | + +### 3.5. Модели данных + +Описать сущности, поля, связи: + +```json +{ + "entity": "Transaction", + "fields": [ + {"name": "id", "type": "UUID", "pk": true}, + {"name": "amount", "type": "Decimal"}, + {"name": "date", "type": "datetime"}, + {"name": "category_id", "type": "UUID", "fk": "Category"} + ] +} +``` + +### 3.6. API интерфейсы + +| Метод | Путь | Описание | Request | Response | Статусы | +|---|---|---|---|---|---| +| GET | /transactions | Список | ?page, ?limit | [Transaction] | 200 | +| POST | /transactions | Создать | CreateTransactionDTO | Transaction | 201, 400 | + +Если GUI — ключевые страницы. Если CLI — команды. + +### 3.7. Обработка ошибок + +- Стратегия: исключения / Result / коды. +- Формат API-ошибок: `{ "error": "...", "code": "...", "details": {} }`. +- Логирование: уровни для разных событий. + +### 3.8. Стратегия тестирования + +- Какие тесты нужны (unit, integration, e2e). +- Как изолировать зависимости. +- Команда запуска тестов. + +### 3.9. Группировка в задачи + +Предварительно наметить задачи по модулям — вход для DECOMPOSITION: + +``` +T1: Инициализация проекта + зависимости +T2: Модель данных (сущности, миграции) +T3: Service (core logic) +T4: API endpoints +T5: Tests +``` + +### 3.10. Создать design-report + +Записать `.agent/context/design-report.md` по шаблону `TEMPLATES/design-report.md`. + +### 3.11. Дополнительно (по команде пользователя) + +Эти шаги **не выполняются автоматически** — только если пользователь явно попросил: + +- **ADR** — вызвать `COMMANDS/adr.md` для ключевых решений. +- **Alternative Architecture** — вызвать `COMMANDS/alt-arch.md` для сравнения. +- **Risk Register** — вызвать `COMMANDS/risk-register.md` для допущений. +- **Red Team** — вызвать `COMMANDS/red-team.md` для атаки на дизайн. + +## Выход + +- `.agent/context/design-report.md` +- Предварительная группировка задач (для DECOMPOSITION) +- Возможно: ADR, risk-register, alt-architecture, red-team-report (если вызывали команды) +- `checkpoints.json: phases.design = "completed"` + +## Критерии завершения + +- [ ] Стек определён +- [ ] High-level архитектура описана +- [ ] Модули и их ответственность описаны +- [ ] Модели данных спроектированы +- [ ] API/интерфейсы описаны (если применимо) +- [ ] Стратегия тестирования определена +- [ ] Задачи предварительно сгруппированы +- [ ] `design-report.md` создан +- [ ] `checkpoints.json` обновлён diff --git a/.agent/src/PROTOCOLS/04_DECOMPOSITION.md b/.agent/src/PROTOCOLS/04_DECOMPOSITION.md new file mode 100644 index 0000000..6b416a3 --- /dev/null +++ b/.agent/src/PROTOCOLS/04_DECOMPOSITION.md @@ -0,0 +1,117 @@ +# Протокол 04: Декомпозиция задач (DECOMPOSITION) + +## Цель + +Разбить цель пользователя (и архитектурный план, если есть) на атомарные, независимо выполнимые задачи. Записать в `manifest.json` + `manifest.md`. + +## Вход + +- `.agent/context/analysis-report.md` +- `.agent/context/design-report.md` (опционально — для greenfield) +- `.agent/roadmap/sources.md` (опционально) +- `.agent/decisions/*.md` (опционально) +- Цель пользователя (goal из `checkpoints.json`) +- `.agent/rules/project-rules.md` — прочитать первым +- `.agent/checkpoints.json` (фаза decomposition: pending) + +## Принципы + +1. **Атомарность** — одна задача = одна логическая единица, выполнимая и проверяемая за один подход. +2. **Независимость (макс.)** — минимизировать зависимости между задачами. +3. **Тестируемость** — каждая задача имеет измеримые acceptance criteria. +4. **Границы** — задача не выходит за пределы `BOUNDARIES.md`. +5. **Порядок** — задачи с зависимостями выполняются строго последовательно. + +## Шаги + +### 4.1. Прочитать правила проекта + +Прочитать `.agent/rules/project-rules.md`, применить. + +### 4.2. Размер задачи + +Задача должна укладываться в **1-2 часа работы агента**. Если крупнее — разбить. + +Признак слишком крупной задачи: +- Нельзя сформулировать acceptance criteria одной строкой. +- Затрагивает 5+ файлов. +- Содержит союзы «и», «а также», «после чего». + +### 4.3. Сверить с roadmap + +Если существует `.agent/roadmap/sources.md`: + +- Задачи из roadmap получают приоритет P0-P3 в соответствии с `sources.md`. +- Задачи без явного источника получают `origin: "decomposition"`. + +### 4.4. Структура задачи + +| Поле | Описание | Пример | +|---|---|---| +| `id` | Уникальный идентификатор | `T1`, `T2` | +| `title` | Что сделать | "Добавить модель User" | +| `description` | Как и зачем | "Создать SQLAlchemy модель..." | +| `type` | Тип | `feature`, `refactor`, `test`, `fix`, `config`, `design`, `docs`, `invariant` | +| `status` | Статус | `pending`, `in_progress`, `completed`, `failed`, `archived` | +| `origin` | Источник | `roadmap:file`, `adr:NNN`, `user:direct`, `agent:analysis`, `decomposition` | +| `files` | Файлы | `["app/models/user.py"]` | +| `depends_on` | Зависимости | `[]` или `["T0"]` | +| `acceptance_criteria` | 3-5 измеримых пунктов | `["Модель проходит миграцию"]` | +| `context` | Доп. информация | `"Смотри app/models/base.py"` | + +**Типы origin:** + +- `roadmap:{filename}` — из FUTURE/ или roadmap +- `adr:{NNN}` — из Architecture Decision Record +- `user:direct` — от пользователя +- `agent:analysis` — выявлено агентом +- `decomposition` — создано при декомпозиции +- `invariant:{adr_id}` — инвариант для ADR (создаётся командой `/invariant-tests`) +- `risk:{R-NNN}` — из Risk Register + +### 4.5. Зелёная декомпозиция (greenfield/scaffold) + +Если есть `design-report.md` — задачи на основе группировки из дизайна: + +1. **T1: init** — инициализация, зависимости, scaffold. +2. **T2..Tn: features** — модули по одному. +3. **Tn+1: tests** — тесты (можно в составе feature). +4. **Tn+2: polish** — документация, форматирование. + +### 4.6. Сортировка + +Задачи в манифесте в порядке выполнения: + +1. Без зависимостей. +2. Чьи зависимости уже выполнены. +3. С наибольшим числом зависимостей. + +### 4.7. Записать manifest + +Создать `.agent/tasks/manifest.json` по шаблону `TEMPLATES/task-manifest.json`. +Создать `.agent/tasks/manifest.md` по шаблону `TEMPLATES/task-manifest.md`. + +### 4.8. Обновить checkpoints + +```json +{ + "phases": { "decomposition": "completed" }, + "tasks": [...], + "last_updated": "" +} +``` + +## Выход + +- `.agent/tasks/manifest.json` +- `.agent/tasks/manifest.md` +- Обновлённый `checkpoints.json` + +## Критерии завершения + +- [ ] Цель разбита на атомарные задачи +- [ ] У каждой задачи — acceptance criteria, origin, files +- [ ] Зависимости корректны (нет циклов) +- [ ] Задачи сверены с roadmap (если `sources.md` существует) +- [ ] `manifest.json` и `manifest.md` созданы +- [ ] `checkpoints.json` обновлён diff --git a/.agent/src/PROTOCOLS/05_EXECUTION.md b/.agent/src/PROTOCOLS/05_EXECUTION.md new file mode 100644 index 0000000..fa049ff --- /dev/null +++ b/.agent/src/PROTOCOLS/05_EXECUTION.md @@ -0,0 +1,136 @@ +# Протокол 05: Исполнение задач (EXECUTION) + +## Цель + +Выполнить задачи из `manifest.json`: реализовать код, написать тесты, закоммитить, создать request — артефакт результата. + +EXECUTION — **циклическая** фаза. Работает, пока есть задачи со статусом `pending` и выполненными `depends_on`. + +## Вход + +- `.agent/tasks/manifest.json` +- `.agent/context/analysis-report.md` +- `.agent/context/design-report.md` (опционально) +- `.agent/decisions/*.md` (опционально) +- `.agent/rules/project-rules.md` — прочитать первым +- `.agent/checkpoints.json` (фаза execution: pending) + +## Шаги (цикл) + +### 5.1. Прочитать правила проекта + +Прочитать `.agent/rules/project-rules.md`, применить. + +### 5.2. Setup окружения (первый запуск) + +Если это первый запуск EXECUTION в сессии: + +- Установить зависимости через штатный пакетный менеджер. +- Запустить сборку / базовые тесты. +- Записать baseline в `.agent/context/baseline-test-report.log`. + +### 5.3. Выбрать задачу + +Найти в `manifest.json` задачу, удовлетворяющую: + +- `status: "pending"` +- Все `depends_on` имеют `status: "completed"` или `"archived"`. + +Если таких нет — EXECUTION завершён, перейти к ожиданию команды пользователя. + +### 5.4. Заблокировать задачу + +В `manifest.json`: + +```json +{ "id": "T1", "status": "in_progress" } +``` + +### 5.5. Исполнить + +- Следовать конвенциям проекта (из ANALYSE). +- Соблюдать `BOUNDARIES.md`. +- Если задача ссылается на ADR — следовать архитектурному решению. +- Писать код + тесты. + +### 5.6. Верифицировать + +- Запустить тесты (все или релевантные). +- Проверить LSP diagnostics на изменённых файлах. +- Убедиться, что acceptance criteria выполнены. + +### 5.7. Закоммитить + +Сделать git-коммит. Сообщение — суть задачи. + +### 5.8. Создать request + +Создать `.agent/requests/active/req-{task_id}.json` по шаблону `TEMPLATES/request.json`: + +```json +{ + "request_id": "req-T1", + "task_id": "T1", + "title": "GET /health endpoint", + "status": "ready_for_review", + "goal": "Добавить ручку GET /health с тестами", + "changes": { + "summary": "Создан health router, подключён в main.py, написаны тесты", + "commits": ["abc1234"], + "files_changed": ["app/routers/health.py", "app/main.py", "tests/test_health.py"] + }, + "verification": { + "tests_passed": "24/24", + "lsp_clean": true + }, + "fulfills_ac": ["Ручка возвращает 200 + {\"status\":\"ok\"}"] +} +``` + +Request фиксирует: +- **summary** — суть изменений (не diff). +- **commits** — ссылки на коммиты. +- **files_changed** — какие файлы. +- **verification** — тесты + LSP. +- **fulfills_ac** — какие acceptance criteria закрыты. + +### 5.9. Завершить задачу + +```json +{ "id": "T1", "status": "completed" } +``` + +### 5.10. Цикл + +Перейти к шагу 5.3. Если задач больше нет — сообщить пользователю и ожидать команду (METASTATE, новая задача, или завершение). + +## Request как единица результата + +Не просто «задача сделана», а документированный результат. Request проходит ревью в фазе METASTATE: + +- `ready_for_review` → после проверки → `approved` или `rejected`. + +## Команды во время EXECUTION + +В любой момент цикла пользователь может вызвать: + +- **/adr** — зафиксировать архитектурное решение, появившееся в процессе. +- **/red-team** — попытаться сломать текущий подход. +- **/risk-register** — зафиксировать новый риск. + +Команды не прерывают EXECUTION, но могут добавить задачи в manifest. + +## Выход + +- Выполненные задачи в `manifest.json` (status: completed) +- `.agent/requests/active/req-{task_id}.json` для каждой выполненной задачи +- Обновлённый `checkpoints.json` + +## Критерии завершения (одна итерация) + +- [ ] Acceptance criteria выполнены +- [ ] Тесты проходят +- [ ] LSP diagnostics чист +- [ ] Коммит создан +- [ ] Request создан в `.agent/requests/active/` +- [ ] Задача в `manifest.json` отмечена completed diff --git a/.agent/src/PROTOCOLS/06_METASTATE.md b/.agent/src/PROTOCOLS/06_METASTATE.md new file mode 100644 index 0000000..dca2fcd --- /dev/null +++ b/.agent/src/PROTOCOLS/06_METASTATE.md @@ -0,0 +1,145 @@ +# Протокол 06: Обновление метасостояния (METASTATE) + +## Цель + +По команде пользователя провести ревью накопленных requests, синхронизировать манифест, обновить слепок проекта и подготовить `.agent/` как полную картину для следующей сессии. + +## Когда запускать + +По команде пользователя: + +- «обнови метасостояние» +- «update metastate» +- «подведи итог» +- «заверши сессию» + +Может запускаться многократно — после каждой группы выполненных задач. + +## Вход + +- `.agent/requests/active/` — все request-ы со статусом `ready_for_review` +- `.agent/tasks/manifest.json` +- `.agent/context/project-state.md` (создан в ANALYSE, обновляется здесь) +- `.agent/roadmap/sources.md` +- `.agent/decisions/index.json` +- `.agent/checkpoints.json` + +## Шаги + +### 6.1. Собрать requests + +Прочитать все файлы из `.agent/requests/active/` со статусом `ready_for_review`. + +### 6.2. Ревью каждого request + +Для каждого: + +1. **Верифицировать** — тесты проходят, LSP чист, AC выполнены, коммиты на месте. +2. **Принять или отклонить:** + + - ✅ **approved**: + - Переместить в `.agent/requests/archive/`. + - В `manifest.json` убедиться: `status: "completed"`. + + - ❌ **rejected**: + - Оставить в `active/` с комментарием. + - В `manifest.json`: `status: "reopened"`, добавить `rejection_reason`. + - В request добавить `rejection_reason`. + +### 6.3. Архивация завершённых задач + +Для каждой `completed` задачи: + +1. Создать `.agent/archive/tasks/{id}.json` — полное описание. +2. В `manifest.json` заменить на one-liner: + + ```json + { "id": "T1", "title": "GET /health endpoint", "status": "archived", "origin": "user:direct" } + ``` + +### 6.4. Обновить project-state + +Переписать `.agent/context/project-state.md` с учётом выполненных задач: + +- Обновить список модулей (добавлены / изменены). +- Обновить архитектурную схему (кратко). +- Обновить статус тестов. +- Добавить новые ADR. +- Убрать закрытые concerns. + +**Цель:** следующий агент читает `project-state.md` и понимает проект, не открывая исходники. + +### 6.5. Обновить roadmap + +В `.agent/roadmap/sources.md`: + +- Отметить выполненные пункты. +- Пересчитать приоритеты. +- Добавить новые источники (если появились). + +### 6.6. Индекс архива + +Создать/обновить `.agent/archive/index.json`: + +```json +{ + "version": "3.0.0", + "archived_at": "", + "tasks": [{ "id": "T1", "title": "...", "archived_at": "" }], + "requests": [{ "id": "req-T1", "task_id": "T1", "archived_at": "" }], + "checkpoints": [{ "file": "checkpoints/.json", "archived_at": "" }] +} +``` + +### 6.7. Создать handoff-summary + +Создать `.agent/handoff-summary.md` — полная сводка для следующего агента: + +```markdown +## Session Summary +**Session:** +**Goal:** +**Completed:** N tasks +**Pending:** M tasks +**Approved requests:** req-T1, req-T2 + +## Project State +(краткая выжимка из project-state.md) + +## Next Steps +(с чего начать следующую сессию) + +## Key Artifacts +- Project state: `.agent/context/project-state.md` +- Tasks: `.agent/tasks/manifest.json` +- Roadmap: `.agent/roadmap/sources.md` +- Pending reviews: `.agent/requests/active/` +- Archive: `.agent/archive/index.json` +``` + +### 6.8. Обновить checkpoints + +```json +{ "phases": { "metastate": "completed" }, "last_updated": "" } +``` + +## Выход + +- `.agent/requests/archive/` — подтверждённые request-ы +- `.agent/archive/tasks/{id}.json` — архив задач +- Обновлённый `.agent/context/project-state.md` +- Обновлённый `.agent/roadmap/sources.md` +- `.agent/handoff-summary.md` +- `.agent/archive/index.json` +- Финальный `checkpoints.json` + +## Критерии завершения + +- [ ] Все `ready_for_review` requests проверены (approved / rejected) +- [ ] Approved перемещены в archive +- [ ] Completed задачи архивированы (one-liner в manifest) +- [ ] `project-state.md` отражает актуальное состояние +- [ ] `roadmap/sources.md` обновлён +- [ ] `archive/index.json` создан +- [ ] `handoff-summary.md` готов +- [ ] `checkpoints.json` финализирован diff --git a/.agent/src/PROTOCOLS/07_HANDOFF.md b/.agent/src/PROTOCOLS/07_HANDOFF.md new file mode 100644 index 0000000..7c7bdb2 --- /dev/null +++ b/.agent/src/PROTOCOLS/07_HANDOFF.md @@ -0,0 +1,113 @@ +# Протокол 07: Завершение сессии (HANDOFF) + +## Цель + +Финализация сессии: валидация структуры `.agent/`, финальный `session-summary.md`, отметка `phases.handoff = "completed"`. + +> Если перед HANDOFF был METASTATE — архивация, project-state, handoff-summary уже готовы. HANDOFF только валидирует и финализирует. + +## Вход + +- `.agent/checkpoints.json` (все фазы кроме handoff: completed или skipped) +- Все артефакты `.agent/` + +## Шаги + +### 7.1. Проверить: был ли METASTATE? + +Если существуют `.agent/handoff-summary.md` и `.agent/context/project-state.md` (обновлён) — METASTATE выполнен. Перейти к шагу 7.3. + +Если нет — выполнить лёгкую архивацию (шаг 7.2). + +### 7.2. Лёгкая архивация (если METASTATE не было) + +Если есть `completed` задачи в `manifest.json`: + +- Архивировать в `.agent/archive/tasks/{id}.json`. +- Заменить в `manifest.json` на one-liner. +- Создать `.agent/archive/index.json`. + +### 7.3. Валидация + +Проверить: + +- [ ] Все фазы в `checkpoints.json` отмечены `completed` или `skipped`. +- [ ] `.agent/` содержит обязательные файлы: + - `checkpoints.json` + - `context/analysis-report.md` + - `context/project-state.md` + - `tasks/manifest.json` + `manifest.md` + - `rules/project-rules.md` + - `src/GUIDE.md` + - `src/BOUNDARIES.md` + - `src/VERSION` + - `src/PROTOCOLS/` + - `src/COMMANDS/` + - `src/TEMPLATES/` +- [ ] В `manifest.json` нет циклических зависимостей. +- [ ] У каждой задачи — measurable acceptance criteria и origin. +- [ ] `AGENTS.md` присутствует в корне репозитория. + +### 7.4. Создать session-summary + +Создать `.agent/session-summary.md`: + +```markdown +# Session Summary + +**Session:** +**MetaAgent version:** 3.0.0 +**Date:** +**Goal:** + +## Phases Executed +- [x] INIT +- [x] ANALYSE +- [x] ROADMAP +- [x] DESIGN (или skipped) +- [x] DECOMPOSITION +- [x] EXECUTION (N tasks) +- [x] METASTATE (или skipped) +- [x] HANDOFF + +## Results +- Tasks completed: N +- Requests approved: N +- Files changed: [list] + +## Next +Следующий агент: читай `.agent/handoff-summary.md`. +``` + +### 7.5. Финализировать checkpoints + +```json +{ "phases": { "handoff": "completed" }, "last_updated": "" } +``` + +### 7.6. Сигнал + +``` +HANDOFF COMPLETE + +Session: +Target: +Type: +Tasks: total, completed, pending + +Следующий агент начинает с .agent/handoff-summary.md +``` + +## Выход + +- `.agent/session-summary.md` +- Финальный `.agent/checkpoints.json` +- (если METASTATE не было) `.agent/archive/index.json` + +## Критерии завершения + +- [ ] Все артефакты на месте +- [ ] (если METASTATE не было) `completed` задачи архивированы +- [ ] `session-summary.md` создан +- [ ] `checkpoints.json` финализирован +- [ ] Сигнал отправлен пользователю diff --git a/.agent/src/TEMPLATES/adr-NNNN.md b/.agent/src/TEMPLATES/adr-NNNN.md new file mode 100644 index 0000000..0d9eec6 --- /dev/null +++ b/.agent/src/TEMPLATES/adr-NNNN.md @@ -0,0 +1,23 @@ +# ADR-NNNN: <Заголовок решения> + +**Статус:** proposed | accepted | deprecated | superseded + +**Дата:** {{ date }} + +**Контекст:** почему возникла необходимость в решении, какая проблема решается. + +**Рассматриваемые альтернативы:** +1. Вариант A — описание +2. Вариант B — описание +3. Вариант C — описание + +**Решение:** выбран вариант . + +**Обоснование:** почему выбран именно этот вариант (критерии: сложность, поддерживаемость, производительность, совместимость). + +**Последствия:** +- Позитивные: ... +- Негативные: ... +- Риски: ... + +**Invariant (если применимо):** ключевое правило, которое не должен нарушать исполнительный агент. Если можно — ссылка на тест, проверяющий invariant. diff --git a/.agent/src/TEMPLATES/analysis-report.md b/.agent/src/TEMPLATES/analysis-report.md new file mode 100644 index 0000000..8bc4080 --- /dev/null +++ b/.agent/src/TEMPLATES/analysis-report.md @@ -0,0 +1,86 @@ +# Analysis Report + +## Session + +- **Session ID:** `{{ session_id }}` +- **Target repo:** `{{ target_repo }}` +- **Date:** {{ date }} +- **Project type:** `{{ project_type }}` (existing / greenfield / scaffold) + +## 1. Общая информация + +- **README:** {{ readme_summary }} +- **Лицензия:** {{ license }} +- **CI/CD:** {{ ci_cd }} +- **Точка входа:** {{ entry_point }} +- **Система сборки:** {{ build_system }} + +## 2. Стек технологий (existing / scaffold) + +| Компонент | Значение | +|---|---| +| Язык | {{ language }} | +| Фреймворк | {{ framework }} | +| База данных | {{ database }} | +| Тестовый раннер | {{ test_runner }} | +| Пакетный менеджер | {{ package_manager }} | +| Линтер/форматтер | {{ linter }} | + +## 3. Архитектура (existing / scaffold) + +``` +{{ directory_tree }} +``` + +**Паттерн:** {{ architecture_pattern }} + +**Ключевые модули:** + +| Модуль | Описание | +|---|---| +| {{ module }} | {{ description }} | + +## 4. Конвенции (existing / scaffold) + +- **Стиль:** {{ code_style }} +- **Импорты:** {{ import_style }} +- **Типизация:** {{ typing_usage }} +- **Обработка ошибок:** {{ error_handling }} +- **Логирование:** {{ logging }} + +## 5. Тесты (existing / scaffold) + +- **Команда запуска:** `{{ test_command }}` +- **Всего тестов:** {{ total_tests }} +- **Пройдено:** {{ passed }} +- **Упало:** {{ failed }} +- **Пропущено:** {{ skipped }} +- **Упавшие тесты:** {{ failed_tests_list }} + +## 6. Базовая проверка (existing / scaffold) + +- **Сборка:** {{ build_status }} +- **Запуск:** {{ run_status }} +- **Git status:** {{ git_status }} + +## 7. Требования (greenfield / scaffold) + +### Функциональные требования + +{{ functional_requirements_list }} + +### Нефункциональные требования + +{{ non_functional_requirements_list }} + +### Бизнес-контекст + +{{ business_context_list }} + +### Неясные моменты / Вопросы + +{{ open_questions_list }} + +## 8. Примечания + +{{ notes }} diff --git a/.agent/src/TEMPLATES/design-report.md b/.agent/src/TEMPLATES/design-report.md new file mode 100644 index 0000000..93782be --- /dev/null +++ b/.agent/src/TEMPLATES/design-report.md @@ -0,0 +1,80 @@ +# Design Report + +## Session + +- **Session ID:** `{{ session_id }}` +- **Target repo:** `{{ target_repo }}` +- **Date:** {{ date }} + +## 1. Технологический стек + +| Компонент | Выбор | Обоснование | +|---|---|---| +| Язык | {{ language }} | {{ language_rationale }} | +| Фреймворк | {{ framework }} | {{ framework_rationale }} | +| База данных | {{ database }} | {{ database_rationale }} | +| Инфраструктура | {{ infrastructure }} | {{ infrastructure_rationale }} | + +## 2. High-Level архитектура + +**Паттерн:** {{ architecture_pattern }} + +``` +{{ architecture_diagram }} +``` + +**Поток данных:** +1. {{ data_flow_step_1 }} +2. {{ data_flow_step_2 }} +3. {{ data_flow_step_3 }} + +## 3. Модули + +| Модуль | Ответственность | Ключевые компоненты | Зависит от | +|---|---|---|---| +| `{{ module_path }}` | {{ responsibility }} | {{ components }} | {{ dependencies }} | + +## 4. Модели данных + +### Сущности + +{{ entity_descriptions }} + +## 5. API / Интерфейсы + +{{ api_endpoints_table }} + +## 6. Обработка ошибок + +- **Стратегия:** {{ error_strategy }} +- **Формат ошибок:** {{ error_format }} +- **Логирование:** {{ logging_strategy }} + +## 7. Тестирование + +- **Unit-тесты:** {{ unit_test_strategy }} +- **Integration-тесты:** {{ integration_test_strategy }} +- **Mock-стратегия:** {{ mock_strategy }} +- **Команда запуска:** `{{ test_command }}` + +## 8. Дополнительные артефакты (по команде пользователя) + +Если пользователь вызвал соответствующие команды, добавить ссылки: + +- ADR: `.agent/decisions/` (команда `/adr`) +- Alternative Architecture: `.agent/context/alt-architecture.md` (команда `/alt-arch`) +- Risk Register: `.agent/context/risk-register.md` (команда `/risk-register`) +- Red Team Review: `.agent/context/red-team-report.md` (команда `/red-team`) + +## 9. Предварительная группировка задач + +| Задача | Описание | Тип | +|---|---|---| +| T1 | {{ task_1 }} | config | +| T2 | {{ task_2 }} | feature | +| T3 | {{ task_3 }} | feature | +| T4 | {{ task_4 }} | test | + +## 10. Примечания + +{{ notes }} diff --git a/.agent/src/TEMPLATES/handoff-summary.md b/.agent/src/TEMPLATES/handoff-summary.md new file mode 100644 index 0000000..3e1f851 --- /dev/null +++ b/.agent/src/TEMPLATES/handoff-summary.md @@ -0,0 +1,57 @@ +# Handoff Summary + +## Session Info + +- **Session ID:** `{{ session_id }}` +- **Target Repo:** {{ target_repo }} +- **Goal:** {{ goal }} +- **Date:** {{ date }} +- **Project type:** {{ project_type }} + +## Repo Summary + +{{ repo_summary }} + +## Artifacts Created + +- **Analysis report:** `.agent/context/analysis-report.md` +- **Project state:** `.agent/context/project-state.md` +- **Design report:** {{ design_report_path_or_dash }} +- **Roadmap:** `.agent/roadmap/sources.md` +- **ADR:** {{ adr_summary_or_dash }} +- **Risk Register:** {{ risk_register_path_or_dash }} +- **Red Team Report:** {{ red_team_report_path_or_dash }} + +## Environment Status + +- **Build:** {{ build_status }} +- **Tests:** {{ tests_passed }}/{{ tests_total }} passed +- **Baseline log:** `.agent/context/baseline-test-report.log` +- **Dependencies:** {{ deps_status }} + +## Task Overview + +| Status | Count | +|---|---| +| Total | {{ total }} | +| Pending | {{ pending }} | +| In Progress | {{ in_progress }} | +| Completed | {{ completed }} | +| Archived | {{ archived }} | +| Failed/Skipped | {{ failed }} | + +## Tasks (ordered) + +{{ task_list_markdown }} + +## Next Steps + +Следующий агент: прочитай `.agent/context/project-state.md`, затем `.agent/tasks/manifest.json` и приступай к первой `pending` задаче. + +## Caveats + +{{ caveats_list }} + +## Checkpoints + +Файл: `.agent/checkpoints.json` — состояние фаз и список задач. diff --git a/.agent/src/TEMPLATES/project-rules.md b/.agent/src/TEMPLATES/project-rules.md new file mode 100644 index 0000000..24ef377 --- /dev/null +++ b/.agent/src/TEMPLATES/project-rules.md @@ -0,0 +1,17 @@ +# Project Rules + +Правила, которым агент обязан следовать во всех фазах. +Добавляйте сюда условия, которые должны соблюдаться всегда — они будут прочитаны +перед началом каждой фазы и учтены при декомпозиции и реализации. + +## Обязательные правила + +- (укажите правила, например: «Всегда использовать tabs для отступов») + +## Запреты + +- (укажите запреты, например: «Не трогать CI/CD конфигурацию») + +## Конвенции проекта + +- (укажите конвенции, например: «Имена классов в PascalCase, функции в snake_case») diff --git a/.agent/src/TEMPLATES/project-state.md b/.agent/src/TEMPLATES/project-state.md new file mode 100644 index 0000000..a14595e --- /dev/null +++ b/.agent/src/TEMPLATES/project-state.md @@ -0,0 +1,43 @@ +# Project State +# Auto-generated — updated by ANALYSE (initial) and METASTATE (on updates) + +**Last updated:** {{ timestamp }} +**Session:** {{ session_id }} + +## Project Type + +{{ project_type }} + +## Tech Stack + +| Category | Technology | +|----------|-----------| +| Language | {{ language }} | +| Framework | {{ framework }} | +| Database | {{ database }} | +| Test runner | {{ test_runner }} | +| Package manager | {{ package_manager }} | + +## Current Architecture + +{{ architecture_description }} + +## Key Modules + +| Module | Status | Description | +|--------|--------|-------------| +| {{ module_name }} | {{ existing / stub / new }} | {{ module_description }} | + +## Decisions in Effect + +| ADR | Decision | Status | +|-----|----------|--------| +| {{ adr_id }} | {{ decision_summary }} | {{ active / superseded }} | + +## Testing Status + +{{ testing_summary }} + +## Open Concerns + +- {{ concern_1 }} diff --git a/.agent/src/TEMPLATES/request.json b/.agent/src/TEMPLATES/request.json new file mode 100644 index 0000000..286922d --- /dev/null +++ b/.agent/src/TEMPLATES/request.json @@ -0,0 +1,29 @@ +{ + "$schema": ".agent/src/TEMPLATES/schemas/request-schema.json", + "template_version": "1.0", + "request_id": "req-{{ task_id }}", + "task_id": "{{ task_id }}", + "title": "{{ task_title }}", + "status": "ready_for_review", + "created_at": "{{ timestamp }}", + "goal": "{{ task_goal }}", + + "changes": { + "summary": "{{ changes_summary }}", + "commits": [ + "{{ commit_hash }}" + ], + "files_changed": [ + "path/to/file.py" + ] + }, + + "verification": { + "tests_passed": "{{ test_results }}", + "lsp_clean": true + }, + + "fulfills_ac": [ + "{{ acceptance_criterion }}" + ] +} diff --git a/.agent/src/TEMPLATES/risk-register.md b/.agent/src/TEMPLATES/risk-register.md new file mode 100644 index 0000000..816dda4 --- /dev/null +++ b/.agent/src/TEMPLATES/risk-register.md @@ -0,0 +1,7 @@ +# Risk Register + +| # | Assumption | Impact if wrong | Mitigation | Review trigger | +|---|---|---|---|---| +| R1 | Пользователи имеют Python 3.11+ | Проект не запускается на старых версиях | Указать требование в README, CI-проверка | При жалобе на установку | +| R2 | JSON-файлы не превышают 10MB | Деградация производительности | Добавить лимит в model.py | При первом замедлении | +| R3 | ... | ... | ... | ... | diff --git a/.agent/src/TEMPLATES/roadmap-sources.md b/.agent/src/TEMPLATES/roadmap-sources.md new file mode 100644 index 0000000..76ef0d7 --- /dev/null +++ b/.agent/src/TEMPLATES/roadmap-sources.md @@ -0,0 +1,42 @@ +# Roadmap Sources +# Auto-generated — created by ROADMAP phase + +**Created:** {{ timestamp }} +**Session:** {{ session_id }} + +## Priority Legend + +- **P0** — Critical, do next +- **P1** — Important, do soon +- **P2** — Nice to have +- **P3** — Future / deferred + +## Sources + +### FUTURE Plans + +| Plan | Priority | Status | Origin File | +|------|----------|--------|-------------| +| {{ plan_title }} | {{ P0-P3 }} | {{ active / archived }} | FUTURE/{{ filename }}.md | + +### ADR-Derived Tasks + +| Source ADR | Task | Priority | +|------------|------|----------| +| {{ adr_id }} | {{ task_description }} | {{ P0-P3 }} | + +### User Requests + +| Request | Priority | Source | +|---------|----------|--------| +| {{ request }} | {{ P0-P3 }} | {{ direct / issue / feedback }} | + +### Agent-Identified Improvements + +| Observation | Suggested Task | Priority | +|-------------|----------------|----------| +| {{ observation }} | {{ task }} | {{ P0-P3 }} | + +## Consolidated Priority Queue + +1. **{{ task_title }}** ({{ origin }}) — {{ priority }} diff --git a/.agent/src/TEMPLATES/schemas/checkpoints-schema.json b/.agent/src/TEMPLATES/schemas/checkpoints-schema.json new file mode 100644 index 0000000..bb0f7a0 --- /dev/null +++ b/.agent/src/TEMPLATES/schemas/checkpoints-schema.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "metaagent/checkpoints/3.0.0", + "title": "MetaAgent Checkpoints", + "description": "Schema for .agent/checkpoints.json — session state", + "type": "object", + "properties": { + "metaagent_version": { + "type": "string", + "description": "MetaAgent version that created this checkpoint", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "session_id": { + "type": "string", + "description": "Unique session identifier" + }, + "target_repo": { + "type": "string", + "description": "Path to the target repository" + }, + "goal": { + "type": ["string", "null"], + "description": "Session goal (set by user, may be null until first task)" + }, + "project_type": { + "type": ["string", "null"], + "enum": [null, "existing", "greenfield", "scaffold"], + "description": "Type of the target project (set in ANALYSE phase)" + }, + "phases": { + "type": "object", + "properties": { + "init": { "type": "string", "enum": ["pending", "in_progress", "completed", "failed", "skipped"] }, + "analyse": { "type": "string", "enum": ["pending", "in_progress", "completed", "failed", "skipped"] }, + "roadmap": { "type": "string", "enum": ["pending", "in_progress", "completed", "failed", "skipped"] }, + "design": { "type": "string", "enum": ["pending", "in_progress", "completed", "failed", "skipped"] }, + "decomposition": { "type": "string", "enum": ["pending", "in_progress", "completed", "failed", "skipped"] }, + "execution": { "type": "string", "enum": ["pending", "in_progress", "completed", "failed", "skipped"] }, + "metastate": { "type": "string", "enum": ["pending", "in_progress", "completed", "failed", "skipped"] }, + "handoff": { "type": "string", "enum": ["pending", "in_progress", "completed", "failed", "skipped"] } + }, + "additionalProperties": false + }, + "tasks": { + "type": "array", + "description": "List of tasks (one-liners after archiving)", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "title": { "type": "string" }, + "status": { "type": "string", "enum": ["pending", "in_progress", "completed", "failed", "archived"] } + }, + "required": ["id", "title", "status"], + "additionalProperties": false + } + }, + "last_updated": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of last update" + } + }, + "required": ["metaagent_version", "session_id", "phases", "last_updated"], + "additionalProperties": false +} diff --git a/.agent/src/TEMPLATES/schemas/decisions-index-schema.json b/.agent/src/TEMPLATES/schemas/decisions-index-schema.json new file mode 100644 index 0000000..99efa4c --- /dev/null +++ b/.agent/src/TEMPLATES/schemas/decisions-index-schema.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "metaagent/decisions-index/3.0.0", + "title": "MetaAgent Decisions Index", + "description": "Schema for .agent/decisions/index.json — machine-readable index of ADRs", + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "Schema version (3.0 in v3.0+, 2.0 still valid from v2.1)", + "enum": ["2.0", "3.0"] + }, + "decisions": { + "type": "array", + "description": "List of architecture decision records", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[0-9]{3,}$", + "description": "ADR number (001, 002, ...)" + }, + "title": { + "type": "string", + "description": "Short title of the decision" + }, + "status": { + "type": "string", + "enum": ["proposed", "accepted", "deprecated", "superseded"], + "description": "ADR status" + }, + "file": { + "type": "string", + "description": "Filename in .agent/decisions/ (e.g. 001-stack.md)" + }, + "date": { + "type": "string", + "format": "date", + "description": "Decision date (ISO 8601)" + } + }, + "required": ["id", "title", "file"], + "additionalProperties": false + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of index creation" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of last update" + } + }, + "required": ["version", "decisions"], + "additionalProperties": false +} diff --git a/.agent/src/TEMPLATES/schemas/task-manifest-schema.json b/.agent/src/TEMPLATES/schemas/task-manifest-schema.json new file mode 100644 index 0000000..9cdb97d --- /dev/null +++ b/.agent/src/TEMPLATES/schemas/task-manifest-schema.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "metaagent/task-manifest/3.0.0", + "title": "MetaAgent Task Manifest", + "description": "Schema for .agent/tasks/manifest.json — the global task manifest", + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "Schema version", + "enum": ["3.0"] + }, + "session_id": { + "type": "string", + "description": "Session ID that created this manifest" + }, + "goal": { + "type": "string", + "description": "Overall goal of the session" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of creation" + }, + "tasks": { + "type": "array", + "description": "List of tasks", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^(T[0-9]+|T-INV-[0-9]+)$", + "description": "Unique task identifier (T1, T2, ... or T-INV-N for invariants)" + }, + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Detailed description" + }, + "type": { + "type": "string", + "enum": ["feature", "refactor", "test", "fix", "config", "design", "docs", "invariant"], + "description": "Task type" + }, + "origin": { + "type": "string", + "description": "Task source (roadmap:file, adr:NNN, user:direct, agent:analysis, decomposition, invariant:NNN, risk:R-NNN)" + }, + "files": { + "type": "array", + "items": { "type": "string" }, + "description": "Files affected by this task" + }, + "depends_on": { + "type": "array", + "items": { "type": "string" }, + "description": "Task IDs this task depends on" + }, + "acceptance_criteria": { + "type": "array", + "items": { "type": "string" }, + "description": "Measurable criteria for completion" + }, + "context": { + "type": "string", + "description": "Additional context or references" + }, + "status": { + "type": "string", + "enum": ["pending", "in_progress", "completed", "failed", "archived"], + "description": "Current task status" + }, + "claimed_by": { + "type": "string", + "description": "Worker ID if claimed" + }, + "claimed_at": { + "type": "string", + "format": "date-time", + "description": "When the task was claimed" + } + }, + "required": ["id", "title", "type", "status"], + "additionalProperties": false + } + } + }, + "required": ["version", "tasks"], + "additionalProperties": false +} diff --git a/.agent/src/TEMPLATES/session-summary.md b/.agent/src/TEMPLATES/session-summary.md new file mode 100644 index 0000000..e56242f --- /dev/null +++ b/.agent/src/TEMPLATES/session-summary.md @@ -0,0 +1,50 @@ +# Session Summary + +**Session:** {{ session_id }} +**Target:** {{ target_repo }} +**MetaAgent version:** {{ version }} +**Date:** {{ date }} + +## Phase Status + +| Phase | Status | +|---|---| +| INIT | {{ init_status }} | +| ANALYSE | {{ analyse_status }} | +| ROADMAP | {{ roadmap_status }} | +| DESIGN | {{ design_status }} | +| DECOMPOSITION | {{ decomposition_status }} | +| EXECUTION | {{ execution_status }} | +| METASTATE | {{ metastate_status }} | +| HANDOFF | {{ handoff_status }} | + +## Tasks + +| Status | Count | +|---|---| +| Total | {{ total }} | +| Pending | {{ pending }} | +| In Progress | {{ in_progress }} | +| Completed | {{ completed }} | +| Archived | {{ archived }} | +| Failed/Skipped | {{ failed }} | + +**By origin:** +- user:direct: {{ user_direct_count }} +- roadmap: {{ roadmap_count }} +- adr: {{ adr_count }} +- decomposition: {{ decomposition_count }} +- (другое): {{ other_count }} + +## Commands Invoked (если были) + +{{ commands_invoked_list }} + +## Quick Links + +- Task Manifest: `.agent/tasks/manifest.json` +- Handoff Summary: `.agent/handoff-summary.md` +- Project State: `.agent/context/project-state.md` +- ADR: `.agent/decisions/` +- Risk Register: `.agent/context/risk-register.md` +- Red Team Report: `.agent/context/red-team-report.md` diff --git a/.agent/src/TEMPLATES/task-manifest.json b/.agent/src/TEMPLATES/task-manifest.json new file mode 100644 index 0000000..579ffbe --- /dev/null +++ b/.agent/src/TEMPLATES/task-manifest.json @@ -0,0 +1,24 @@ +{ + "$schema": ".agent/src/TEMPLATES/schemas/task-manifest-schema.json", + "version": "3.0", + "session_id": "{{ session_id }}", + "goal": "{{ goal }}", + "created_at": "{{ timestamp }}", + "tasks": [ + { + "id": "T1", + "title": "{{ task_title }}", + "description": "{{ task_description }}", + "type": "feature|refactor|test|fix|config|design|docs|invariant", + "origin": "user:direct", + "files": ["path/to/file1.py", "path/to/file2.py"], + "depends_on": [], + "acceptance_criteria": [ + "Критерий 1: ...", + "Критерий 2: ..." + ], + "context": "Дополнительная информация", + "status": "pending" + } + ] +} diff --git a/.agent/src/TEMPLATES/task-manifest.md b/.agent/src/TEMPLATES/task-manifest.md new file mode 100644 index 0000000..c3c3c5a --- /dev/null +++ b/.agent/src/TEMPLATES/task-manifest.md @@ -0,0 +1,42 @@ +# Task Manifest + +**Session:** {{ session_id }} +**Goal:** {{ goal }} +**Date:** {{ timestamp }} + +--- + +## Task Overview + +| ID | Title | Type | Depends On | Status | +|---|---|---|---|---| +| T1 | {{ title }} | {{ type }} | — | pending | +| T2 | {{ title }} | {{ type }} | T1 | pending | + +**Total tasks:** {{ count }} + +--- + +## Task Details + +### T1: {{ title }} + +**Type:** {{ type }} +**Description:** {{ description }} + +**Files:** +- `{{ file_path }}` + +**Depends on:** — + +**Acceptance Criteria:** +- [ ] {{ criterion }} +- [ ] {{ criterion }} + +**Context:** {{ context }} + +--- + +### T2: {{ title }} + +... diff --git a/.agent/src/VERSION b/.agent/src/VERSION new file mode 100644 index 0000000..4a36342 --- /dev/null +++ b/.agent/src/VERSION @@ -0,0 +1 @@ +3.0.0 diff --git a/.agent/src/WORKFLOW.md b/.agent/src/WORKFLOW.md new file mode 100644 index 0000000..be9f87a --- /dev/null +++ b/.agent/src/WORKFLOW.md @@ -0,0 +1,240 @@ +# WORKFLOW — Сквозной пример сессии v3.0 + +--- + +## Сценарий: рефакторинг auth-модуля + +**Цель:** Вынести логику из `auth/login.py` (450 строк, монолит) в отдельные модули `auth/router.py`, `auth/schemas.py`, `auth/deps.py`. + +**Целевой репозиторий:** `github.com/example/fastapi-app` + +**Пользователь:** «Вынеси авторизацию в отдельные модули». + +**MetaAgent:** v3.0.0 + +--- + +### PROJECT LOOP + +#### INIT + +Агент читает `AGENTS.md`, переходит в `.agent/src/GUIDE.md`. Понимает цикл. Создаёт `.agent/`, копирует исходники, инициализирует `checkpoints.json`: + +```json +{ + "metaagent_version": "3.0.0", + "session_id": "ses_v30_001", + "target_repo": "/tmp/fastapi-app", + "goal": "Вынести авторизацию в auth/{router,schemas,deps}.py", + "project_type": null, + "phases": { + "init": "completed", + "analyse": "pending", + "roadmap": "pending", + "design": "pending", + "decomposition": "pending", + "execution": "pending", + "metastate": "pending", + "handoff": "pending" + }, + "tasks": [], + "last_updated": "2026-10-08T15:00:00Z" +} +``` + +#### ANALYSE + +Агент сканирует проект: + +- Стек: Python 3.12, FastAPI, SQLAlchemy, pytest. +- `auth/login.py` — 450 строк, монолит (цель рефакторинга). +- Тесты: 48 passed (baseline). + +Создаёт: + +- `.agent/context/analysis-report.md` +- `.agent/context/project-state.md` (начальный) + +`checkpoints.json`: `project_type = "existing"`, `phases.analyse = "completed"`. + +#### ROADMAP + +- `FUTURE/` — пусто. +- `.agent/decisions/` — пусто. +- Единственный источник — пользовательский запрос. + +Создаёт `.agent/roadmap/sources.md`: + +```markdown +## User Requests +| Вынести авторизацию | P0 | user:direct | + +## Consolidated Priority Queue +1. Вынести auth/ (user:direct) — P0 +``` + +`phases.roadmap = "completed"`. + +#### DESIGN + +**Пропускается** (existing-проект). `phases.design = "skipped"`. + +#### DECOMPOSITION + +Задачи: + +```json +{ + "tasks": [ + { + "id": "T1", + "title": "Создать auth/router.py", + "origin": "user:direct", + "files": ["app/auth/router.py"], + "depends_on": [], + "acceptance_criteria": [ + "Роуты авторизации вынесены из auth/login.py", + "auth/router.py экспортирует router", + "Существующие тесты проходят" + ], + "status": "pending" + }, + { + "id": "T2", + "title": "Создать auth/schemas.py", + "origin": "user:direct", + "files": ["app/auth/schemas.py"], + "depends_on": ["T1"], + "acceptance_criteria": [ + "Pydantic схемы вынесены в auth/schemas.py", + "Существующие тесты проходят" + ], + "status": "pending" + }, + { + "id": "T3", + "title": "Создать auth/deps.py", + "origin": "user:direct", + "files": ["app/auth/deps.py"], + "depends_on": ["T1"], + "acceptance_criteria": [ + "Dependency injection функции вынесены в auth/deps.py", + "Существующие тесты проходят" + ], + "status": "pending" + } + ] +} +``` + +`phases.decomposition = "completed"`. + +--- + +### WORK LOOP (первая итерация) + +#### EXECUTION — задача T1 + +1. Берёт T1 (`pending`, нет зависимостей). +2. `status = "in_progress"`. +3. Создаёт `app/auth/router.py` — переносит роуты. +4. Тесты: 48/48. +5. Коммит: `abc1234 — refactor: extract auth router`. +6. Создаёт request: + +```json +{ + "request_id": "req-T1", + "task_id": "T1", + "title": "Создать auth/router.py", + "status": "ready_for_review", + "goal": "Вынести роуты авторизации", + "changes": { + "summary": "Роуты авторизации вынесены из auth/login.py в auth/router.py", + "commits": ["abc1234"], + "files_changed": ["app/auth/router.py", "app/auth/__init__.py", "tests/test_auth_router.py"] + }, + "verification": { "tests_passed": "48/48", "lsp_clean": true }, + "fulfills_ac": ["Роуты вынесены", "Тесты проходят"] +} +``` + +7. `T1 → completed`. + +#### EXECUTION — задача T2 + +Создаёт `auth/schemas.py`, request `req-T2`. T2 → completed. + +#### EXECUTION — задача T3 + +Создаёт `auth/deps.py`, request `req-T3`. T3 → completed. + +Задачи закончились. Агент ждёт команду. + +--- + +### METASTATE (по команде пользователя) + +**Пользователь:** «обнови метасостояние». + +1. **Ревью requests:** три request-а, все approved. + - `req-T1`, `req-T2`, `req-T3` → `.agent/requests/archive/`. + +2. **Архивация задач:** + - T1, T2, T3 → `.agent/archive/tasks/`. + - В `manifest.json` — one-liner: `status: "archived"`. + +3. **Обновление project-state.md:** + + ```markdown + ## Key Modules + | Module | Status | Description | + |--------|--------|-------------| + | app/auth/router.py | new | Вынесенные роуты | + | app/auth/schemas.py | new | Pydantic схемы | + | app/auth/deps.py | new | Dependency injection | + ``` + +4. **Создание handoff-summary.md:** + + ```markdown + ## Session Summary + **Goal:** Рефакторинг авторизации + **Completed:** 3/3 tasks + **Approved requests:** req-T1, req-T2, req-T3 + + ## Project State + auth разбит на router + schemas + deps. + Исходный auth/login.py: 450 → 120 строк. + + ## Next Steps + - Проверить, не осталось ли прямых импортов из старого login.py + - Обновить main.py если нужно + ``` + +--- + +### HANDOFF + +``` +HANDOFF COMPLETE + +Session: ses_v30_001 +Target: /tmp/fastapi-app +Type: existing +Tasks: 3/3 completed + +Следующий агент начинает с .agent/handoff-summary.md +``` + +--- + +## Использование команд в процессе + +В любой момент сессии пользователь мог вызвать: + +- **«запиши это как ADR»** → `COMMANDS/adr.md` создал бы `.agent/decisions/001-modular-auth.md`. +- **«red team»** → `COMMANDS/red-team.md` создал бы `.agent/context/red-team-report.md` с попыткой сломать новую структуру. +- **«risk register»** → `COMMANDS/risk-register.md` зафиксировал бы допущения (например, «считаем, что порядок middleware не важен»). + +Команды **не обязательны**. Если не вызваны — `.agent/decisions/`, `risk-register.md`, `red-team-report.md` не создаются. diff --git a/.agent/src/install.ps1 b/.agent/src/install.ps1 new file mode 100644 index 0000000..c986b46 --- /dev/null +++ b/.agent/src/install.ps1 @@ -0,0 +1,365 @@ +#!/usr/bin/env pwsh +# MetaAgent — установка исходников в целевой проект +# Usage: .\install.ps1 [[-Path] target_path] [-Check] [-Update] + +param( + [string]$Path = "", + [switch]$Check, + [switch]$Update, + [switch]$Help +) + +$MetaAgentSrc = Split-Path -Parent $MyInvocation.MyCommand.Path + +# --- helpers --- +function Write-Info { Write-Host " →" -NoNewline -ForegroundColor Blue; Write-Host " $args" } +function Write-Ok { Write-Host " ✓" -NoNewline -ForegroundColor Green; Write-Host " $args" } +function Write-Skip { Write-Host " −" -NoNewline -ForegroundColor Yellow; Write-Host " $args" } +function Write-Warn { Write-Host " ⚠" -NoNewline -ForegroundColor Yellow; Write-Host " $args" } +function Write-Fail { Write-Host " ✗" -NoNewline -ForegroundColor Red; Write-Host " $args" } +function Write-Header { param([string]$Label) + Write-Host "" + Write-Host ("─" * 40) + Write-Host " $Label" + Write-Host ("─" * 40) +} + +function Show-Usage { + @" +Usage: install.ps1 [[-Path] target_path] [-Check] [-Update] [-Help] + +Install MetaAgent sources into /.agent/src/ + +Options: + -Path Path to target project (default: interactive prompt) + -Check Dry-run: only check target readiness, no install + -Update Overwrite existing files in .agent/src/ + -Help Show this help + +Examples: + .\install.ps1 + .\install.ps1 -Path C:\Projects\MyApp + .\install.ps1 -Path C:\Projects\MyApp -Check + .\install.ps1 -Path C:\Projects\MyApp -Update +"@ + exit 0 +} + +if ($Help) { Show-Usage } + +# --- resolve target --- +$TargetPath = $Path +if (-not $TargetPath) { + $TargetPath = Read-Host "Enter path to target project" +} +$TargetPath = $TargetPath.Trim() + +# --- pre-flight ----------------------------------------------------------- +Write-Header "Pre-flight" + +# 1. target exists? +if (-not (Test-Path $TargetPath -PathType Container)) { + Write-Fail "Target directory '$TargetPath' does not exist." + exit 1 +} +$TargetPath = (Resolve-Path $TargetPath).Path +Write-Ok "Target: $TargetPath" + +# 2. write permission? (try to create a temp file as probe) +$probe = [System.IO.Path]::Combine($TargetPath, ".metaagent_probe.tmp") +try { + [System.IO.File]::WriteAllBytes($probe, [byte[]]@()) + Remove-Item $probe -Force + Write-Ok "Write permission: yes" +} catch { + Write-Fail "No write permission on '$TargetPath'." + exit 1 +} + +# 3. already installed? compare versions +$AgentDir = Join-Path $TargetPath ".agent" +$SrcDir = Join-Path $AgentDir "src" +$VersionFile = Join-Path $MetaAgentSrc "VERSION" + +$Version = if (Test-Path $VersionFile -PathType Leaf) { + (Get-Content $VersionFile -Raw -Encoding UTF8).Trim() +} else { "?" } + +$oldVerPath = Join-Path $SrcDir "VERSION" +if (Test-Path $oldVerPath -PathType Leaf) { + $oldVer = (Get-Content $oldVerPath -Raw -Encoding UTF8).Trim() + if ($oldVer -ne $Version) { + Write-Info "Existing MetaAgent v$oldVer found → upgrading to v$Version" + } else { + Write-Skip "MetaAgent v${Version} already installed (use -Update to reinstall)" + if (-not $Check) { + Write-Warn "No changes applied. Run with -Update to overwrite existing files." + } + } +} else { + Write-Info "Fresh install: MetaAgent v$Version" +} + +# 4. summary +$AgentsMd = Join-Path $TargetPath "AGENTS.md" +$RulesDir = Join-Path $AgentDir "rules" +$DecisionsDir = Join-Path $AgentDir "decisions" +$TasksDir = Join-Path $AgentDir "tasks" +$ContextDir = Join-Path $AgentDir "context" +$ArchiveDir = Join-Path $AgentDir "archive" +$RequestsDir = Join-Path $AgentDir "requests" +$RoadmapDir = Join-Path $AgentDir "roadmap" +$TempDir = Join-Path $TargetPath ".temp" + +$DirList = @( + $SrcDir, $RulesDir, $DecisionsDir, $TasksDir, + (Join-Path $TasksDir "backlog"), $ContextDir, + $ArchiveDir, + (Join-Path $ArchiveDir "tasks"), + (Join-Path $ArchiveDir "decisions"), + (Join-Path $ArchiveDir "checkpoints"), + (Join-Path $RequestsDir "active"), + (Join-Path $RequestsDir "archive"), + $RoadmapDir, + (Join-Path $RoadmapDir "archive"), + $TempDir +) + +if ($Check) { + Write-Host "" + Write-Info "--check mode: all checks passed, no changes applied." + exit 0 +} + +# --- phase 1: directories ------------------------------------------------ +Write-Header "Directories" + +foreach ($d in $DirList) { + $null = New-Item -ItemType Directory -Path $d -Force + $short = $d.Replace("$TargetPath\", "") + if (Test-Path $d -PathType Container) { + Write-Ok $short + } else { + Write-Fail "$short (creation failed)" + } +} + +# --- phase 2: files ------------------------------------------------------ +Write-Header "Files" + +$copyCount = 0 +$skipCount = 0 +$failCount = 0 + +function Copy-File { + param([string]$Src, [string]$DstDir) + $name = Split-Path $Src -Leaf + $dst = Join-Path $DstDir $name + if (-not (Test-Path $Src -PathType Leaf)) { + Write-Skip "$name (source not found)" + $script:skipCount++ + return + } + if ($Update -or -not (Test-Path $dst)) { + try { + Copy-Item $Src $dst -Force -ErrorAction Stop + Write-Ok $name + $script:copyCount++ + } catch { + Write-Fail $name + $script:failCount++ + } + } else { + Write-Skip "$name (exists, use -Update to overwrite)" + $script:skipCount++ + } +} + +function Copy-Dir { + param([string]$Src, [string]$DstDir) + $name = Split-Path $Src -Leaf + $dst = Join-Path $DstDir $name + if (-not (Test-Path $Src -PathType Container)) { + Write-Skip "$name/ (source not found)" + $script:skipCount++ + return + } + $null = New-Item -ItemType Directory -Path $dst -Force + try { + if ($Update) { + Get-ChildItem $Src | ForEach-Object { + Copy-Item $_.FullName $dst -Recurse -Force -ErrorAction Stop + } + } else { + Get-ChildItem $Src | ForEach-Object { + $targetPath = Join-Path $dst $_.Name + if (-not (Test-Path $targetPath)) { + Copy-Item $_.FullName $dst -Recurse -ErrorAction Stop + } + } + } + Write-Ok "$name/" + $script:copyCount++ + } catch { + Write-Fail "$name/ (partial copy)" + $script:failCount++ + } +} + +Copy-File (Join-Path $MetaAgentSrc "GUIDE.md") $SrcDir +Copy-File (Join-Path $MetaAgentSrc "BOUNDARIES.md") $SrcDir +Copy-File (Join-Path $MetaAgentSrc "CHANGELOG.md") $SrcDir +Copy-File (Join-Path $MetaAgentSrc "WORKFLOW.md") $SrcDir +Copy-File (Join-Path $MetaAgentSrc "VERSION") $SrcDir +Copy-Dir (Join-Path $MetaAgentSrc "PROTOCOLS") $SrcDir +Copy-Dir (Join-Path $MetaAgentSrc "COMMANDS") $SrcDir +Copy-Dir (Join-Path $MetaAgentSrc "TEMPLATES") $SrcDir +Copy-File (Join-Path $MetaAgentSrc "install.sh") $SrcDir +Copy-File (Join-Path $MetaAgentSrc "install.ps1") $SrcDir + +# --- phase 3: AGENTS.md -------------------------------------------------- +Write-Header "AGENTS.md" + +if (-not (Test-Path $AgentsMd -PathType Leaf)) { + $content = @" +# MetaAgent + +Этот проект использует [MetaAgent](.agent/src/GUIDE.md) v$Version — +набор инструкций для AI-агента. + +## Контекст MetaAgent + +| Ресурс | Путь | +|--------|------| +| Главная инструкция | `.agent/src/GUIDE.md` | +| Протоколы фаз | `.agent/src/PROTOCOLS/` | +| Команды (on-demand) | `.agent/src/COMMANDS/` | +| Шаблоны артефактов | `.agent/src/TEMPLATES/` | +| Границы (что разрешено/запрещено) | `.agent/src/BOUNDARIES.md` | +| История версий | `.agent/src/CHANGELOG.md` | +| Правила проекта | `.agent/rules/project-rules.md` | +| Пример работы | `.agent/src/WORKFLOW.md` | +| Версия | `.agent/src/VERSION` | + +## Состояние сессии (если инициализировано) + +| Артефакт | Путь | +|----------|------| +| Чекпоинты сессии | `.agent/checkpoints.json` | +| Слепок проекта | `.agent/context/project-state.md` | +| Анализ репозитория | `.agent/context/analysis-report.md` | +| Дорожная карта | `.agent/roadmap/sources.md` | +| Манифест задач | `.agent/tasks/manifest.json` | +| Сводка для следующего агента | `.agent/handoff-summary.md` | +| Сводка сессии | `.agent/session-summary.md` | + +## Для агента + +Жизненный цикл MetaAgent v$Version: + +``` +INIT → ANALYSE → ROADMAP → [DESIGN] → DECOMPOSITION → EXECUTION → METASTATE → HANDOFF +``` + +1. **Прочитай** `.agent/src/GUIDE.md` — пойми цикл и доступные команды. +2. **Прочитай** `.agent/src/BOUNDARIES.md` — соблюдай границы. +3. **Прочитай** `.agent/rules/project-rules.md` — выполни правила пользователя. +4. **Проверь** `.agent/checkpoints.json` — если существует, используй как состояние сессии. +5. **Проверь** `.agent/context/project-state.md` — получи актуальную картину. +6. **Проверь** `.agent/tasks/manifest.json` — если существует, выполняй задачи по порядку. +7. Если `.agent/` не инициализирован или устарел — запусти `install.sh --update` для + обновления исходников MetaAgent до актуальной версии. + +## Команды (on-demand) + +В любой момент пользователь может вызвать: +- `/adr` — записать архитектурное решение +- `/red-team` — попытаться сломать дизайн +- `/risk-register` — зафиксировать допущения +- `/alt-arch` — описать альтернативу +- `/invariant-tests` — тесты-инварианты для ADR +"@ + $utf8 = [System.Text.Encoding]::UTF8 + [System.IO.File]::WriteAllBytes($AgentsMd, $utf8.GetBytes($content)) + Write-Ok "AGENTS.md created" +} elseif ($Update) { + $content = @" +# MetaAgent + +Этот проект использует [MetaAgent](.agent/src/GUIDE.md) v$Version — +набор инструкций для AI-агента. + +## Контекст MetaAgent + +| Ресурс | Путь | +|--------|------| +| Главная инструкция | `.agent/src/GUIDE.md` | +| Протоколы фаз | `.agent/src/PROTOCOLS/` | +| Команды (on-demand) | `.agent/src/COMMANDS/` | +| Шаблоны артефактов | `.agent/src/TEMPLATES/` | +| Границы (что разрешено/запрещено) | `.agent/src/BOUNDARIES.md` | +| История версий | `.agent/src/CHANGELOG.md` | +| Правила проекта | `.agent/rules/project-rules.md` | +| Пример работы | `.agent/src/WORKFLOW.md` | +| Версия | `.agent/src/VERSION` | + +## Состояние сессии (если инициализировано) + +| Артефакт | Путь | +|----------|------| +| Чекпоинты сессии | `.agent/checkpoints.json` | +| Слепок проекта | `.agent/context/project-state.md` | +| Анализ репозитория | `.agent/context/analysis-report.md` | +| Дорожная карта | `.agent/roadmap/sources.md` | +| Манифест задач | `.agent/tasks/manifest.json` | +| Сводка для следующего агента | `.agent/handoff-summary.md` | +| Сводка сессии | `.agent/session-summary.md` | + +## Для агента + +Жизненный цикл MetaAgent v$Version: + +``` +INIT → ANALYSE → ROADMAP → [DESIGN] → DECOMPOSITION → EXECUTION → METASTATE → HANDOFF +``` + +1. **Прочитай** `.agent/src/GUIDE.md` — пойми цикл и доступные команды. +2. **Прочитай** `.agent/src/BOUNDARIES.md` — соблюдай границы. +3. **Прочитай** `.agent/rules/project-rules.md` — выполни правила пользователя. +4. **Проверь** `.agent/checkpoints.json` — если существует, используй как состояние сессии. +5. **Проверь** `.agent/context/project-state.md` — получи актуальную картину. +6. **Проверь** `.agent/tasks/manifest.json` — если существует, выполняй задачи по порядку. +7. Если `.agent/` не инициализирован или устарел — запусти `install.sh --update` для + обновления исходников MetaAgent до актуальной версии. + +## Команды (on-demand) + +В любой момент пользователь может вызвать: +- `/adr` — записать архитектурное решение +- `/red-team` — попытаться сломать дизайн +- `/risk-register` — зафиксировать допущения +- `/alt-arch` — описать альтернативу +- `/invariant-tests` — тесты-инварианты для ADR +"@ + $utf8 = [System.Text.Encoding]::UTF8 + [System.IO.File]::WriteAllBytes($AgentsMd, $utf8.GetBytes($content)) + Write-Ok "AGENTS.md updated" +} else { + Write-Skip "AGENTS.md (exists, use -Update to overwrite)" + $script:skipCount++ +} + +# --- summary ------------------------------------------------------------- +Write-Header "Summary" +Write-Host " MetaAgent v$Version → $SrcDir" +Write-Host "" +if ($copyCount -gt 0) { Write-Ok "$copyCount file(s) copied" } +if ($skipCount -gt 0) { Write-Skip "$skipCount file(s) skipped" } +if ($failCount -gt 0) { Write-Fail "$failCount file(s) failed" } +Write-Host "" +if ($failCount -eq 0) { + Write-Ok "Installation completed successfully." +} else { + Write-Fail "Installation completed with $failCount error(s)." + exit 1 +} diff --git a/.agent/src/install.sh b/.agent/src/install.sh new file mode 100644 index 0000000..7acd278 --- /dev/null +++ b/.agent/src/install.sh @@ -0,0 +1,312 @@ +#!/usr/bin/env bash +# MetaAgent — установка исходников в целевой проект +# Usage: ./install.sh [--check|--update] [target_path] +set -euo pipefail + +METAAGENT_SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# --- helpers --- +red=; grn=; ylw=; blu=; rst= +if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then + red=$(tput setaf 1); grn=$(tput setaf 2) + ylw=$(tput setaf 3); blu=$(tput setaf 4) + rst=$(tput sgr0) +fi +info() { echo " ${blu}→${rst} $*"; } +ok() { echo " ${grn}✓${rst} $*"; } +skip() { echo " ${ylw}−${rst} $*"; } +warn() { echo " ${ylw}⚠${rst} $*"; } +fail() { echo " ${red}✗${rst} $*"; } +header(){ echo; echo "────────────────────────────────────────"; echo " $*"; echo "────────────────────────────────────────"; } + +usage() { + cat </.agent/src/ + +Options: + --check, -c Dry-run: only check target readiness, no install + --update, -u Overwrite existing files in .agent/src/ + --help, -h Show this help + +Examples: + $0 + $0 /path/to/project + $0 --check /path/to/project + $0 --update /path/to/project +EOF + exit 0 +} + +# --- arg parsing --- +CHECK=false +UPDATE=false +TARGET_PATH="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --check|-c) CHECK=true; shift ;; + --update|-u) UPDATE=true; shift ;; + --help|-h) usage ;; + --*) echo "${red}Unknown option:${rst} $1"; usage ;; + *) TARGET_PATH="$1"; shift ;; + esac +done + +# --- resolve target --- +if [[ -z "$TARGET_PATH" ]]; then + read -r -p "Enter path to target project: " TARGET_PATH +fi +TARGET_PATH="${TARGET_PATH/#\~/$HOME}" + +# --- pre-flight ----------------------------------------------------------- +header "Pre-flight" + +# 1. target exists? +if [[ ! -d "$TARGET_PATH" ]]; then + fail "Target directory '$TARGET_PATH' does not exist." + exit 1 +fi + +# resolve to absolute path +TARGET_PATH="$(cd "$TARGET_PATH" 2>/dev/null && pwd)" || { + fail "Cannot access '$TARGET_PATH'." + exit 1 +} +ok "Target: $TARGET_PATH" + +# 2. write permission? +if [[ ! -w "$TARGET_PATH" ]]; then + fail "No write permission on '$TARGET_PATH'." + exit 1 +fi +ok "Write permission: yes" + +# 3. already installed? compare versions +AGENT_DIR="$TARGET_PATH/.agent" +SRC_DIR="$AGENT_DIR/src" +VERSION="$(cat "$METAAGENT_SRC/VERSION" 2>/dev/null || echo '?')" + +if [[ -f "$SRC_DIR/VERSION" ]]; then + OLD_VER="$(cat "$SRC_DIR/VERSION" 2>/dev/null || echo '?')" + if [[ "$OLD_VER" != "$VERSION" ]]; then + info "Existing MetaAgent v${OLD_VER} found → upgrading to v${VERSION}" + else + skip "MetaAgent v${VERSION} already installed (use --update to reinstall)" + if [[ "$CHECK" == false ]]; then + warn "No changes applied. Run with --update to overwrite existing files." + fi + fi +else + info "Fresh install: MetaAgent v$VERSION" +fi + +# 4. summary +AGENTS_MD="$TARGET_PATH/AGENTS.md" +RULES_DIR="$AGENT_DIR/rules" +DECISIONS_DIR="$AGENT_DIR/decisions" +TASKS_DIR="$AGENT_DIR/tasks" +CONTEXT_DIR="$AGENT_DIR/context" +ARCHIVE_DIR="$AGENT_DIR/archive" +ARCHIVE_TASKS_DIR="$ARCHIVE_DIR/tasks" +ARCHIVE_DECISIONS_DIR="$ARCHIVE_DIR/decisions" +ARCHIVE_CHECKPOINTS_DIR="$ARCHIVE_DIR/checkpoints" +REQUESTS_DIR="$AGENT_DIR/requests" +REQUESTS_ACTIVE_DIR="$REQUESTS_DIR/active" +REQUESTS_ARCHIVE_DIR="$REQUESTS_DIR/archive" +ROADMAP_DIR="$AGENT_DIR/roadmap" +ROADMAP_ARCHIVE_DIR="$ROADMAP_DIR/archive" +TEMP_DIR="$TARGET_PATH/.temp" + +if [[ "$CHECK" == true ]]; then + echo "" + info "${ylw}--check mode:${rst} all checks passed, no changes applied." + exit 0 +fi + +# --- phase 1: directories ------------------------------------------------ +header "Directories" + +mkdir -p "$SRC_DIR" "$RULES_DIR" "$DECISIONS_DIR" "$TASKS_DIR" "$TASKS_DIR/backlog" \ + "$CONTEXT_DIR" "$ARCHIVE_DIR" "$ARCHIVE_TASKS_DIR" "$ARCHIVE_DECISIONS_DIR" \ + "$ARCHIVE_CHECKPOINTS_DIR" \ + "$REQUESTS_ACTIVE_DIR" "$REQUESTS_ARCHIVE_DIR" \ + "$ROADMAP_DIR" "$ROADMAP_ARCHIVE_DIR" \ + "$TEMP_DIR" + +for d in "$SRC_DIR" "$RULES_DIR" "$DECISIONS_DIR" "$TASKS_DIR" "$TASKS_DIR/backlog" \ + "$CONTEXT_DIR" "$ARCHIVE_DIR" "$ARCHIVE_TASKS_DIR" "$ARCHIVE_DECISIONS_DIR" \ + "$ARCHIVE_CHECKPOINTS_DIR" \ + "$REQUESTS_ACTIVE_DIR" "$REQUESTS_ARCHIVE_DIR" \ + "$ROADMAP_DIR" "$ROADMAP_ARCHIVE_DIR" \ + "$TEMP_DIR"; do + short="${d#$TARGET_PATH/}" + if [[ -d "$d" ]]; then + ok "$short" + else + fail "$short (creation failed)" + fi +done + +# --- phase 2: files ------------------------------------------------------ +header "Files" + +COPY_COUNT=0 +SKIP_COUNT=0 +FAIL_COUNT=0 + +copy_file() { + local src="$1" dst_dir="$2" + local name; name="$(basename "$src")" + local dst="$dst_dir/$name" + if [[ ! -f "$src" ]]; then + skip "$name (source not found)" + SKIP_COUNT=$((SKIP_COUNT + 1)) + return + fi + if [[ "$UPDATE" == true ]] || [[ ! -f "$dst" ]]; then + if cp "$src" "$dst"; then + ok "$name" + COPY_COUNT=$((COPY_COUNT + 1)) + else + fail "$name" + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi + else + skip "$name (exists, use --update to overwrite)" + SKIP_COUNT=$((SKIP_COUNT + 1)) + fi +} + +copy_dir() { + local src="$1" dst_dir="$2" + local name; name="$(basename "$src")" + local dst="$dst_dir/$name" + if [[ ! -d "$src" ]]; then + skip "$name/ (source not found)" + SKIP_COUNT=$((SKIP_COUNT + 1)) + return + fi + mkdir -p "$dst" + if [[ "$UPDATE" == true ]]; then + if cp -rf "$src"/* "$dst/" 2>/dev/null; then + ok "$name/" + COPY_COUNT=$((COPY_COUNT + 1)) + else + fail "$name/ (partial copy)" + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi + else + cp -rn "$src"/* "$dst/" 2>/dev/null || true + ok "$name/" + COPY_COUNT=$((COPY_COUNT + 1)) + fi +} + +copy_file "$METAAGENT_SRC/GUIDE.md" "$SRC_DIR" +copy_file "$METAAGENT_SRC/BOUNDARIES.md" "$SRC_DIR" +copy_file "$METAAGENT_SRC/CHANGELOG.md" "$SRC_DIR" +copy_file "$METAAGENT_SRC/WORKFLOW.md" "$SRC_DIR" +copy_file "$METAAGENT_SRC/VERSION" "$SRC_DIR" +copy_dir "$METAAGENT_SRC/PROTOCOLS" "$SRC_DIR" +copy_dir "$METAAGENT_SRC/COMMANDS" "$SRC_DIR" +copy_dir "$METAAGENT_SRC/TEMPLATES" "$SRC_DIR" +copy_file "$METAAGENT_SRC/install.sh" "$SRC_DIR" +copy_file "$METAAGENT_SRC/install.ps1" "$SRC_DIR" + +# --- phase 3: AGENTS.md -------------------------------------------------- +header "AGENTS.md" + +create_agents_md() { + cat > "$1" << AGENTS_EOF +# MetaAgent + +Этот проект использует [MetaAgent](.agent/src/GUIDE.md) v$VERSION — +набор инструкций для AI-агента. + +## Контекст MetaAgent + +| Ресурс | Путь | +|--------|------| +| Главная инструкция | \`.agent/src/GUIDE.md\` | +| Протоколы фаз | \`.agent/src/PROTOCOLS/\` | +| Команды (on-demand) | \`.agent/src/COMMANDS/\` | +| Шаблоны артефактов | \`.agent/src/TEMPLATES/\` | +| Границы (что разрешено/запрещено) | \`.agent/src/BOUNDARIES.md\` | +| История версий | \`.agent/src/CHANGELOG.md\` | +| Правила проекта | \`.agent/rules/project-rules.md\` | +| Пример работы | \`.agent/src/WORKFLOW.md\` | +| Версия | \`.agent/src/VERSION\` | + +## Состояние сессии (если инициализировано) + +| Артефакт | Путь | +|----------|------| +| Чекпоинты сессии | \`.agent/checkpoints.json\` | +| Слепок проекта | \`.agent/context/project-state.md\` | +| Анализ репозитория | \`.agent/context/analysis-report.md\` | +| Дорожная карта | \`.agent/roadmap/sources.md\` | +| Манифест задач | \`.agent/tasks/manifest.json\` | +| Сводка для следующего агента | \`.agent/handoff-summary.md\` | +| Сводка сессии | \`.agent/session-summary.md\` | + +## Для агента + +Жизненный цикл MetaAgent v$VERSION: + +\`\`\` +INIT → ANALYSE → ROADMAP → [DESIGN] → DECOMPOSITION → EXECUTION → METASTATE → HANDOFF +\`\`\` + +1. **Прочитай** \`.agent/src/GUIDE.md\` — пойми цикл и доступные команды. +2. **Прочитай** \`.agent/src/BOUNDARIES.md\` — соблюдай границы. +3. **Прочитай** \`.agent/rules/project-rules.md\` — выполни правила пользователя. +4. **Проверь** \`.agent/checkpoints.json\` — если существует, используй как состояние сессии. +5. **Проверь** \`.agent/context/project-state.md\` — получи актуальную картину. +6. **Проверь** \`.agent/tasks/manifest.json\` — если существует, выполняй задачи по порядку. +7. Если \`.agent/\` не инициализирован или устарел — запусти \`install.sh --update\` для + обновления исходников MetaAgent до актуальной версии. + +## Команды (on-demand) + +В любой момент пользователь может вызвать: +- \`/adr\` — записать архитектурное решение +- \`/red-team\` — попытаться сломать дизайн +- \`/risk-register\` — зафиксировать допущения +- \`/alt-arch\` — описать альтернативу +- \`/invariant-tests\` — тесты-инварианты для ADR +AGENTS_EOF +} + +if [[ ! -f "$AGENTS_MD" ]]; then + create_agents_md "$AGENTS_MD" + ok "AGENTS.md created" +elif [[ "$UPDATE" == true ]]; then + create_agents_md "$AGENTS_MD" + ok "AGENTS.md updated" +else + skip "AGENTS.md (exists, use --update to overwrite)" + SKIP_COUNT=$((SKIP_COUNT + 1)) +fi + +# --- summary ------------------------------------------------------------- +header "Summary" +echo " MetaAgent v$VERSION → $SRC_DIR" +echo "" +if (( COPY_COUNT > 0 )); then + ok "${COPY_COUNT} file(s) copied" +fi +if (( SKIP_COUNT > 0 )); then + skip "${SKIP_COUNT} file(s) skipped" +fi +if (( FAIL_COUNT > 0 )); then + fail "${FAIL_COUNT} file(s) failed" +fi +echo "" +if (( FAIL_COUNT == 0 )); then + ok "Installation completed successfully." +else + fail "Installation completed with ${FAIL_COUNT} error(s)." + exit 1 +fi diff --git a/.agent/tasks/manifest.json b/.agent/tasks/manifest.json new file mode 100644 index 0000000..a8ee556 --- /dev/null +++ b/.agent/tasks/manifest.json @@ -0,0 +1,272 @@ +{ + "metaagent_version": "3.0.0", + "session_id": "metaagent-init-2026-10-09", + "target_repo": "S:/Git/nixos", + "goal": "Установить metaagent, перенести накопленные данные (AGENTS.md, docs/arch/*) в структуру .agent/.", + "project_type": "existing", + "date": "2026-10-09T20:30", + "phases": { + "init": "completed", + "analyse": "pending", + "roadmap": "pending", + "design": "skipped", + "decomposition": "pending", + "execution": "pending", + "metastate": "pending", + "handoff": "pending" + }, + "tasks": [ + { + "id": "T1", + "title": "A1: mobile.nix импортирует несуществующий lib/xlib.nix", + "type": "fix", + "status": "pending", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "nix flake check проходит на .#nixOnDroidConfigurations.epral", + "configurations/mobile.nix:12 использует import ../lib/xlib" + ], + "files": ["configurations/mobile.nix"], + "blocks": ["T15", "T16"], + "notes": "Правка как в configurations/default.nix:5. До правки epral мертв." + }, + { + "id": "T2", + "title": "A2: убедиться, что nix flake check вообще запускается", + "type": "verify", + "status": "pending", + "origin": "user:direct", + "depends_on": ["T1"], + "acceptance_criteria": [ + "nix flake check зелёный (после T1)", + "checks в deploy покрывают всё дерево outputs" + ], + "files": ["deploy/default.nix"], + "blocks": ["T15"] + }, + { + "id": "T3", + "title": "A3: явная финальная политика nftables на VDS", + "type": "fix", + "status": "pending", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "nft list ruleset на otreca показывает явное последнее правило или policy", + "firewall.* либо выключен, либо синхронизирован с nftables (не оба сразу)", + "ssh с внешнего адреса работает" + ], + "files": ["configurations/vds.nix"], + "blocks": ["T11", "T12"], + "notes": "Сначала диагностика: nft list ruleset, systemctl status nftables firewall-nftables. Политика — белый список (предпочтительно) или мягкий вариант с явным финальным правилом." + }, + { + "id": "T4", + "title": "B1: guard на несмонтированный носитель /mnt/services", + "type": "security", + "status": "pending", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "В xlib/helpers.nix есть mkStorageGuard", + "postgresql, samba, homebox, gitea, navidrome, syncthing, uptime-kuma, immich, nextcloud, calibre-web, 3x-ui, tape-rotation используют mkStorageGuard", + "Имитация отказа: umount /mnt/services + systemctl start postgresql → FAIL, а не пустая база", + "В AGENTS.md добавлена строка про findmnt перед рестартом (уже в R4)" + ], + "files": [ + "lib/xlib/helpers.nix", + "modules/server/postgresql.nix", + "modules/server/samba.nix", + "modules/server/homebox.nix", + "modules/server/gitea.nix", + "modules/server/navidrome.nix", + "modules/server/syncthing.nix", + "modules/server/uptime-kuma.nix", + "modules/server/immich.nix", + "modules/server/nextcloud.nix", + "modules/server/calibre-web.nix", + "modules/containers/3x-ui.nix", + "modules/containers/tape-rotation.nix" + ], + "blocks": [], + "notes": "ConditionPathIsMountPoint=/mnt/services НЕ использовать (bind-mount внутри одной ФС не меняет st_dev). Надёжны requiresMountsFor или ConditionPathIsMountPoint на xlib.dirs.server-home." + }, + { + "id": "T5", + "title": "B2: зафиксировать, что бэкапов в конфигурации нет", + "type": "documentation", + "status": "pending", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "В project-rules.md (R1.x) явно сказано, что бэкапы вне Nix", + "В project-state.md Open Concerns указано, что бэкапы — внешние" + ], + "files": [".agent/rules/project-rules.md", ".agent/context/project-state.md"], + "blocks": [], + "notes": "Ждёт ответа 5.6 — где бэкапы и как проверять. Не код, а запись." + }, + { + "id": "T6", + "title": "C1: вернуть расследование 3x-ui, потерянное при откате", + "type": "documentation", + "status": "pending", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "git show 9974784:modules/containers/3x-ui-migration-notes.md > .agent/decisions/notes/3x-ui-xray-26.9.md (или аналогичный путь)", + "Файл дополнен вердиктом: миграция 26.7 → 26.9 провалена, откат осознанный, причина — X25519MLKEM768" + ], + "files": [".agent/decisions/notes/3x-ui-xray-26.9.md"], + "blocks": [], + "notes": "200 строк удалены в 22a19be. git show 9974784:... — восстановить." + }, + { + "id": "T7", + "title": "C2: зафиксировать фактические версии панели и ядра 3x-ui", + "type": "investigation", + "status": "pending", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "podman images ... | grep 3x-ui — записано", + "podman inspect ghcr.io/mhsanaei/3x-ui — RepoDigests записано", + "podman exec 3xui_app /app/bin/xray-linux-amd64 version — записано", + "В modules/containers/3x-ui.nix:54 :latest заменён на конкретный тег/digest" + ], + "files": ["modules/containers/3x-ui.nix"], + "blocks": ["T8"] + }, + { + "id": "T8", + "title": "C3: убрать сервис автообновления 3x-ui", + "type": "fix", + "status": "pending", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "podman-update-3xui_app удалён из modules/containers/3x-ui.nix", + "Закомментированный таймер (строки 97-103) удалён", + "Оставлен комментарий-предупреждение" + ], + "files": ["modules/containers/3x-ui.nix"], + "blocks": [], + "notes": "Обновление панели через pull = путь, по которому в 2026-10-04 декларация разошлась с рантаймом. Автоматизировать нельзя." + }, + { + "id": "T9", + "title": "C4: записать в project-rules, что ядро Xray — состояние панели, а не Nix", + "type": "documentation", + "status": "pending", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "В project-rules.md (R1.x) явно сказано: версия ядра Xray выбирается в UI панели и лежит в её sqlite-БД, то есть вне Nix", + "Перед деплоем/рестартом 3x-ui проверять версию ядра в панели" + ], + "files": [".agent/rules/project-rules.md"], + "blocks": [] + }, + { + "id": "T10", + "title": "C5: решить судьбу reality443Forwarding", + "type": "decision", + "status": "pending", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "Решение: (а) оставить + описать в инвариантах, или (б) погасить опцию в vds/default.nix + убрать из options.nix, или (в) довести до рабочего состояния", + "Решение зафиксировано в .agent/decisions/" + ], + "files": ["modules/vds/default.nix", "modules/options.nix", "modules/containers/3x-ui.nix"], + "blocks": [], + "notes": "Связано с 6.9." + }, + { + "id": "T11", + "title": "D1: пробросы роутера — главный недостающий инвариант", + "type": "documentation", + "status": "pending", + "origin": "user:direct", + "depends_on": ["T3"], + "acceptance_criteria": [ + "В project-rules.md (R1.3) формулировка: «Экспозиция наружу определяется пробросами на роутере, не openFirewall. На sapphira networking.firewall.enable = false намеренно. Список пробросов: 22, 80, 443, 8443 (3x-ui/Xray REALITY), 22000 (syncthing). Новый сервис не становится доступен из интернета, пока не добавлен проброс.» (уже сделано)", + "В project-state.md Network секция содержит список пробросов" + ], + "files": [".agent/rules/project-rules.md", ".agent/context/project-state.md"], + "blocks": [] + }, + { + "id": "T12", + "title": "D2: зафиксировать 100.64.0.0 как Tailscale-адрес sapphira", + "type": "documentation", + "status": "pending", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "В project-rules.md (R1.4) инвариант сформулирован (уже сделано)", + "Список из 4 мест, которые придётся править при смене: modules/server/nginx.nix, modules/server/nextcloud.nix, modules/vds/systemd.nix, modules/vds/nginx.nix" + ], + "files": [".agent/rules/project-rules.md"], + "blocks": [] + }, + { + "id": "T13", + "title": "D3: убрать мёртвое правило firewall на sapphira", + "type": "fix", + "status": "pending", + "origin": "user:direct", + "depends_on": ["T11"], + "acceptance_criteria": [ + "modules/server/nginx.nix:225-228 (allowedTCPPorts = [80 443]) удалено или помечено комментарием «депенит от D1»" + ], + "files": ["modules/server/nginx.nix"], + "blocks": [] + }, + { + "id": "T14", + "title": "E1: написать AGENTS.md в корне (с metaagent-шапкой)", + "type": "documentation", + "status": "completed", + "origin": "user:direct", + "depends_on": [], + "acceptance_criteria": [ + "AGENTS.md содержит: yaml frontmatter (для Obsidian dataview), metaagent-указатель, краткую выжимку nixos (хосты, инварианты, ловушки, куда лезть, проверки, где не лезть)" + ], + "files": ["AGENTS.md"], + "blocks": [], + "notes": "Сделано в этой инициализации (см. объединённый AGENTS.md)." + }, + { + "id": "T15", + "title": "E2: выбрать проверки, которые заменят половину инвариантов", + "type": "decision", + "status": "pending", + "origin": "user:direct", + "depends_on": ["T2"], + "acceptance_criteria": [ + "Список из 7 кандидатов (см. analysis-report.md §5) отфильтрован владельцем", + "Выбранные проверки превращены в CI или git pre-commit hook" + ], + "files": [], + "blocks": [] + }, + { + "id": "T16", + "title": "E3: судьба 15 закомментированных модулей в modules/server/default.nix:33-47", + "type": "refactor", + "status": "pending", + "origin": "user:direct", + "depends_on": ["T1"], + "acceptance_criteria": [ + "Решение: удалить или оставить как референс", + "Если удалять — то 15 модулей (remnawave, coturn, mealie, memos, minecraft, n8n, netdata, nfs, open-webui, rsync, step-ca, stirling-pdf, transmission, trilium, zerotier) перенесены в archive или удалены" + ], + "files": ["modules/server/default.nix"], + "blocks": [] + } + ], + "backlog_count": 23, + "backlog_note": "Открытые вопросы из roadmap/sources.md (F: 2.2, 2.5, 2.6, 3.2, 4.1, 4.2, 4.3, 4.4, 4.5, 5.1, 5.3, 5.4, 5.5, 5.6, 6.6, 6.7, 6.8, 6.9, 7.4, 8.2, 8.4, 8.5, 8.6) ждут ответа владельца и станут задачами после ответа." +} diff --git a/.agent/tasks/manifest.md b/.agent/tasks/manifest.md new file mode 100644 index 0000000..248072d --- /dev/null +++ b/.agent/tasks/manifest.md @@ -0,0 +1,86 @@ +# Task Manifest + +**Session:** `metaagent-init-2026-10-09` +**Goal:** Установить metaagent, перенести накопленные данные (AGENTS.md, docs/arch/*) в структуру `.agent/`. +**Date:** 2026-10-09T20:30 +**Project type:** `existing` + +--- + +## Task Overview + +| ID | Title | Type | Depends On | Status | Origin | +|---|---|---|---|---|---| +| T1 | A1: mobile.nix импортирует несуществующий lib/xlib.nix | fix | — | pending | user:direct | +| T2 | A2: убедиться, что nix flake check вообще запускается | verify | T1 | pending | user:direct | +| T3 | A3: явная финальная политика nftables на VDS | fix | — | pending | user:direct | +| T4 | B1: guard на несмонтированный носитель /mnt/services | security | — | pending | user:direct | +| T5 | B2: зафиксировать, что бэкапов в конфигурации нет | docs | — | pending | user:direct | +| T6 | C1: вернуть расследование 3x-ui, потерянное при откате | docs | — | pending | user:direct | +| T7 | C2: зафиксировать фактические версии панели и ядра 3x-ui | investigate | — | pending | user:direct | +| T8 | C3: убрать сервис автообновления 3x-ui | fix | — | pending | user:direct | +| T9 | C4: записать, что ядро Xray — состояние панели, а не Nix | docs | — | pending | user:direct | +| T10 | C5: решить судьбу reality443Forwarding | decision | — | pending | user:direct | +| T11 | D1: пробросы роутера — главный недостающий инвариант | docs | T3 | pending | user:direct | +| T12 | D2: зафиксировать 100.64.0.0 как Tailscale-адрес sapphira | docs | — | pending | user:direct | +| T13 | D3: убрать мёртвое правило firewall на sapphira | fix | T11 | pending | user:direct | +| T14 | E1: написать AGENTS.md в корне (с metaagent-шапкой) | docs | — | **completed** | user:direct | +| T15 | E2: выбрать проверки, которые заменят половину инвариантов | decision | T2 | pending | user:direct | +| T16 | E3: судьба 15 закомментированных модулей | refactor | T1 | pending | user:direct | + +**Total tasks:** 16 +**Pending:** 15 +**In progress:** 0 +**Completed:** 1 +**Backlog (ждут ответа):** 23 + +--- + +## Задачи по группам + +### A. Блокеры (T1–T3) + +- **T1 (A1)** — критично: до правки `epral` мёртв. Цена правки: одна строка в `mobile.nix:12`. +- **T2 (A2)** — диагностика: ловит ли `flake check` проблему из T1. +- **T3 (A3)** — опасная зона: править `nftables` без `nft list ruleset` на otreca = риск отрезать SSH. + +### B. Защита данных (T4–T5) + +- **T4 (B1)** — 12+ сервисов на пустой БД после рестарта без диска. **Самый крупный фикс** (правка `mkStorageGuard` + 12 потребителей). +- **T5 (B2)** — запись в project-rules, не код. Ждёт ответа 5.6. + +### C. 3x-ui: заморозить рабочее состояние (T6–T10) + +- **T6 (C1)** — восстановить 200 строк из коммита `9974784` + дописать вердикт. +- **T7 (C2)** — сначала диагностика на sapphira и otreca, потом запинить тег. +- **T8 (C3)** — удалить `podman-update-3xui_app` + закомментированный таймер. +- **T9 (C4)** — запись в project-rules (R1.x). +- **T10 (C5)** — связано с вопросом 6.9. + +### D. Сетевая граница (T11–T13) + +- **T11 (D1)** — запись в project-rules (R1.3) + project-state.md. +- **T12 (D2)** — запись в project-rules (R1.4). +- **T13 (D3)** — мелкая чистка мёртвого правила. + +### E. Документация (T14–T16) + +- **T14 (E1)** — **выполнено** в этой инициализации. +- **T15 (E2)** — выбор из 7 кандидатов на CI-проверки (см. `analysis-report.md §5`). +- **T16 (E3)** — рефакторинг 15 модулей. + +### F. Backlog (ждут ответа) + +23 вопроса из `roadmap/sources.md` (F: 2.2, 2.5, 2.6, 3.2, 4.1, 4.2, 4.3, 4.4, 4.5, 5.1, 5.3, 5.4, 5.5, 5.6, 6.6, 6.7, 6.8, 6.9, 7.4, 8.2, 8.4, 8.5, 8.6) — становятся задачами после ответа владельца. + +--- + +## Зависимости + +``` +T1 → T2 → T15 +T1 → T16 +T3 → T11 → T13 +``` + +Остальные задачи можно делать параллельно. diff --git a/.gitignore b/.gitignore index 883f7e8..f8bdd5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode .omo __pycache__ -scripts \ No newline at end of file +scripts +.temp diff --git a/AGENTS.md b/AGENTS.md index bc80a13..7709b30 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,97 +2,90 @@ aliases: [] cssclasses: date-created: 2026-10-09T19:01 -date-modified: 2026-10-09T20:23 -tags: [] +date-modified: 2026-10-09T20:30 +tags: [metaagent, nixos, agents] --- # AGENTS.md -NixOS-конфиг домашнего флота. 6 NixOS-хостов + Android (`nix-on-droid`). +Этот проект использует [MetaAgent](.agent/src/GUIDE.md) v3.0.0 — набор +инструкций для AI-агента. -Этот файл — то, что агент должен прочитать **до** первого изменения. Если задача -выглядит так, что требует сломать что-то из «Подтверждённых инвариантов» или -«Ловушек» ниже — остановиться и спросить. +> NixOS-конфиг домашнего флота. 6 NixOS-хостов + Android (`nix-on-droid`). +> Этот файл — то, что агент должен прочитать **до** первого изменения. Если +> задача выглядит так, что требует сломать что-то из «Подтверждённых +> инвариантов» или «Ловушек» ниже — остановиться и спросить. + +## Контекст MetaAgent + +| Ресурс | Путь | +|--------|------| +| Главная инструкция | `.agent/src/GUIDE.md` | +| Протоколы фаз | `.agent/src/PROTOCOLS/` | +| Команды (on-demand) | `.agent/src/COMMANDS/` | +| Шаблоны артефактов | `.agent/src/TEMPLATES/` | +| Границы (что разрешено/запрещено) | `.agent/src/BOUNDARIES.md` | +| История версий | `.agent/src/CHANGELOG.md` | +| Правила проекта (полные) | `.agent/rules/project-rules.md` | +| Слепок проекта | `.agent/context/project-state.md` | +| Анализ репозитория | `.agent/context/analysis-report.md` | +| Дорожная карта | `.agent/roadmap/sources.md` | +| Манифест задач | `.agent/tasks/manifest.json` | +| ADR (архитектурные решения) | `.agent/decisions/` | +| Пример работы | `.agent/src/WORKFLOW.md` | +| Версия | `.agent/src/VERSION` | ## Архитектура (30 секунд) ``` flake.nix ├── configurations/ ← реестр хостов (1 запись = 1 машина) -│ ├── default.nix ← hosts + xlibLib + mkSystem -│ ├── .nix ← модульное тело хоста -│ └── hardware/.nix +├── modules/ ← essentials + per-type (desktop/server/vds/wsl/containers/termux) ├── home/ ← home-manager (per device-type) -├── modules/ -│ ├── options.nix ← кросс-модульные опции -│ ├── default.nix ← defaultModule + strictModule (для nix-on-droid) -│ ├── essentials/ ← packages, services, settings, ssh, shell, systemd-routines -│ ├── desktop/, server/, server/├── vds/, wsl/, containers/, termux/, other/ -├── lib/ -│ ├── mkSystem.nix ← nixosSystem + specialArgs(xlib, inputs) -│ └── xlib/ ← чистые данные: devices, dirs, helpers -├── overlays/, pkgs/, deploy/, secrets/ (sops) -└── .sops.yaml ← один age-ключ на secrets/.(yaml|json|env|ini) +├── lib/xlib/ ← чистые данные: devices, dirs, helpers +├── deploy/, secrets/ (sops), overlays/, pkgs/ +└── .agent/ ← MetaAgent state (rules, decisions, tasks, context, requests, roadmap) ``` -`xlib` (в `lib/xlib/`) — чистые данные: identity (`device`), capability flags, -директории, helper'ы. Передаётся в каждый модуль через `specialArgs`. Конфиг не -может переопределить `xlib` — единственная точка изменения это `configurations/default.nix`. +Подробная карта: `.agent/context/project-state.md` и `.agent/context/analysis-report.md`. ## Хосты -| Attr / имя | device.type | Роль | Деплой | Примечание | -|---|---|---|---|---| -| `default` (nixos) | minimal | Шаблон / минималка | — | hostname `"nixos"` | -| `atoridu` | primary | Основной десктоп | — | xanmod | -| `rydiwo` | secondary | Ноутбук Chuwi MiniBook (xanmod, NTFS) | deploy-rs | `stateVersion 26.05` | -| `otrecа` | vds | VPS, SSH только по Tailscale | deploy-rs | nftables, DHCP, no firewall в NixOS | -| `sapphira` | server | Домашний сервер (белый IP через роутер) | deploy-rs | `firewall.enable = false` намеренно | -| `wsl` | wsl | WSL NixOS на vetymae | — | nixos-wsl module | -| `epral` | termux | Android (`nix-on-droid`) | — | через `mobile.nix`, отдельный модульный путь | - -`device.type` ∈ { minimal, primary, secondary, server, vds, wsl, termux }. -`modules/defaultModule` импортирует `modules//` через `lib.optional -(!isDesktop && type != "minimal") (./. + "/${type}")`. +| Attr / имя | device.type | Роль | Деплой | stateVersion | Примечание | +|---|---|---|---|---|---| +| `default` (nixos) | minimal | Шаблон / минималка | — | — | hostname `"nixos"` | +| `atoridu` | primary | Основной десктоп | — (manual) | 26.05 | xanmod, mini-PC | +| `rydiwo` | secondary | Chuwi MiniBook | deploy-rs | 26.05 | xanmod, NTFS `lamet-drive` | +| `otrecа` | vds | VPS | deploy-rs | 25.05 | Tailscale-only SSH, nftables (см. T3) | +| `sapphira` | server | Домашний сервер | deploy-rs | 25.05 | `firewall.enable = false` намеренно | +| `wsl` | wsl | WSL NixOS | — (manual) | 24.11 | на vetymae (Windows 192.168.1.100) | +| `epral` | termux | Android | — | 24.05 | nix-on-droid, через `mobile.nix` | ## Подтверждённые инварианты -1. **Все `outputs` флейка должны вычисляться.** `configurations/mobile.nix:12` - импортировал несуществующий `lib/xlib.nix` — был сломан, `epral` не - собирался. Зафиксировать через `nix flake check`. -2. **Носитель данных (`/home/oqyude/External`) обязан быть смонтирован** до - старта `postgresql`, `n8n`, `samba`, `homebox`, `minecraft`, `3x-ui`, - `tape-rotation`. `mkServiceStorage` даёт `bind,x-systemd.automount,nofail` - — без guard'а сервис стартует на пустой БД. → todo B1. -3. **Сетевая граница sapphira — роутер.** `firewall.enable = false` намеренно. - Роутер пробрасывает ровно 5 портов: **443, 80, 22000 (syncthing), 8443 - (xray), 22 (ssh)**. `nginx.nix:225` (`allowedTCPPorts = [80 443]`) мёртв. - `openFirewall`/`allowedTCPPorts` на sapphira не имеют эффекта. -5. **`100.64.0.0` = Tailscale-адрес sapphira**, назначен вручную. Не сеть, не - ошибка. Используется в `nginx.nix`, `nextcloud.nix` (`trusted_proxies`), - `vds/systemd.nix`, `vds/nginx.nix`. При смене — править 4 файла. -4. **3x-ui заморожен.** Панель на последней версии (образ `:latest` → запинить), - ядро Xray на 26.7.x. Миграция на 26.9.x провалена. Обходные скрипты (тimer, - migrateScript) отключены осознанно. **Не** обновлять ядро через панель без - записи в `docs/arch/notes/3x-ui-xray-26.9.md`. -6. **nftables на VDS требует явной финальной политики.** Текущий ruleset - (`vds.nix:73-91`) — без явного последнего правила и без `policy` → неявный - accept. На otreca одновременно `nftables.enable = true` и `firewall.*` — - проверить, кто реально владеет ruleset'ом, перед правкой. +> Полные формулировки (с «Где» и «Почему») — в `.agent/rules/project-rules.md` (R1). + +1. **Все `outputs` флейка должны вычисляться.** `configurations/mobile.nix:12` импортировал несуществующий `lib/xlib.nix` — был сломан, `epral` не собирался. → задача T1. +2. **External-диск обязан быть смонтирован** до старта `postgresql`, `n8n`, `samba`, `homebox`, `minecraft`, `3x-ui`, `tape-rotation`. → задача T4. +3. **Сетевая граница sapphira — роутер.** 5 портов: **443, 80, 22000 (syncthing), 8443 (xray), 22 (ssh)**. `firewall.enable = false` намеренно. → задача T11. +4. **`100.64.0.0` = Tailscale-адрес sapphira**, назначен вручную. В 4 файлах. → задача T12. +5. **3x-ui заморожен.** Панель на `:latest`, ядро Xray на 26.7.x. Миграция на 26.9.x провалена. → задачи T6–T10. +6. **nftables на VDS требует явной финальной политики.** Текущий ruleset — без финального правила → неявный accept. → задача T3. +7. **sops-пути — через `config.sops.secrets..path`.** Любой `path =` override на sops-блоке делает хардкод-потребителя молча сломанным. → ADR-0001. ## Ловушки (выглядит сломанным, намеренно) | Где | Что выглядит ошибкой | На самом деле | |---|---|---| -| `server.nix:130` | `firewall.enable = false` при 20 сервисах на `0.0.0.0` | Роутер фильтрует, см. §4 | +| `server.nix:130` | `firewall.enable = false` при 20 сервисах на `0.0.0.0` | Роутер фильтрует, см. инв. 3 | | `mobile.nix:95`, `wsl.nix:59` | `stateVersion` 24.05 / 24.11 vs 26.05 | Каждый хост зафиксирован на своей версии | | `users.nix:66` | `uid = if hostname == "sapphira" then 1001 else …` | Костыль под 1000 = удалённый `yuyus`; удалять только после миграции ФС | | `3x-ui.nix:54` | `image = …:latest` | Панель намеренно latest; ядро Xray — на 26.7.x | -| `3x-ui.nix:33-35` | `reality443Forwarding = true` на VDS | Следствие отката `c8d4a12`; смысл утрачен, см. todo C5 | -| `server/default.nix:33-47` | 15 закомментированных модулей | Отключены осознанно, см. todo E3 | -| `opencode.nix:339` | `systemd.user.services.opencode-web.Service` | `serviceConfig` рендерится в секцию `[serviceConfig]`, systemd молча игнорирует (`c73a698`) | -| `vds.nix:73-91` | nftables без финального правила | Известный пробел, см. todo A3 | -| `100.64.0.0` | Первый адрес CGNAT `/10` | Tassigned вручную, см. §5 | -| `server.nix:61-63` | `z /mnt/services 0777` | World-writable точка монтирования; см. todo B1 | +| `3x-ui.nix:33-35` | `reality443Forwarding = true` на VDS | Следствие отката `c8d4a12`; см. задачу T10 | +| `server/default.nix:33-47` | 15 закомментированных модулей | Отключены осознанно, см. задачу T16 | +| `opencode.nix:339` | `systemd.user.services.opencode-web.Service` | `serviceConfig` рендерится в секцию `[serviceConfig]`, systemd молча игнорирует; см. R2 | +| `vds.nix:73-91` | nftables без финального правила | Известный пробел, см. задачу T3 | +| `100.64.0.0` | Первый адрес CGNAT `/10` | Tailscale-адрес sapphira, см. инв. 4 | +| `server.nix:61-63` | `z /mnt/services 0777` | World-writable точка монтирования; см. задачу T4 | ## Куда лезть по задаче @@ -100,11 +93,11 @@ flake.nix |---|---| | Добавить хост | `configurations/default.nix` + `configurations/.nix` + `configurations/{hardware,disko}/.nix` | | Добавить системный сервис | `modules/server/.nix`, добавить в `modules/server/default.nix:imports` | -| Добавить home-пакет для пользователя | `home/.nix` (через `lib.mkIf` или просто список) | -| Добавить опцию, читаемую несколькими модулями | `modules/options.nix` | +| Добавить home-пакет | `home/.nix` | +| Добавить кросс-модульную опцию | `modules/options.nix` | | Изменить mount/имя пользователя | `lib/xlib/dirs.nix`, `lib/xlib/device.nix` | | Изменить домен / сертификат | `modules/server/coredns.nix` + `modules/server/nginx.nix` (или `vds/`) | -| Sops-секрет | положить в `secrets/.`; `users.nix:99` уже подключает `secrets/default.yaml`; dotenv/json-секреты — через `mkUserSecret` | +| Sops-секрет | `secrets/.`; `users.nix:99` подключает `secrets/default.yaml`; dotenv/json — через `mkUserSecret` | ## Проверки @@ -122,9 +115,6 @@ nix build .#nixOnDroidConfigurations.epral.config.system.build.toplevel findmnt /home/oqyude/External findmnt /mnt/services -# state of guard-зависимостей (когда будет todo B1) -systemctl show postgresql -p Requires -p After | tr ' ' '\n' | grep -E 'mnt-|home-oqyude' - # sops sops --version ``` @@ -132,13 +122,24 @@ sops --version ## Где НЕ лезть без ответа владельца - `secrets/` (sops-encrypted, расшифровываются `/etc/ssh/id_ed25519` → циклический bootstrap). -- `lдet deploy` без проверки deploy-rs нод: `rydiwo` (ноутбук, может быть выключен). +- `let deploy` без проверки deploy-rs нод: `rydiwo` (ноутбук, может быть выключен). - Любая правка, противоречащая «Подтверждённым инвариантам» выше. +- Ядро Xray 26.7.x → 26.9.x — миграция провалена, не повторять без отдельной задачи. -## Дальше читать +## Команды MetaAgent (on-demand) -- `docs/arch/map.md` — полная карта: per-host детали, сетевая топология, - инвентарь сервисов, все известные open questions. -- `docs/arch/invariants.md` — слои 9–11 (home-manager, deploy, формат) + - полный список неотвеченных вопросов слоёв 1–8. -- `docs/arch/todo.md` — задачи A1–F (правки и документирование). +- `/adr` — записать архитектурное решение +- `/red-team` — попытаться сломать дизайн +- `/risk-register` — зафиксировать допущения +- `/alt-arch` — описать альтернативу +- `/invariant-tests` — тесты-инварианты для ADR + +## Жизненный цикл MetaAgent v3.0.0 + +``` +INIT → ANALYSE → ROADMAP → [DESIGN] → DECOMPOSITION → EXECUTION → METASTATE → HANDOFF +``` + +Текущее состояние: см. `.agent/checkpoints.json` (`phases.init = completed`, +`phases.analyse = completed`, `phases.roadmap = completed`, +`phases.decomposition = completed`, `phases.execution = in_progress`). diff --git a/docs/arch/map.md b/docs/arch/map.md deleted file mode 100644 index 8269a1f..0000000 --- a/docs/arch/map.md +++ /dev/null @@ -1,396 +0,0 @@ -# Карта архитектуры - -Полная карта репозитория: per-host детали, сетевая топология, инвентарь сервисов. -Слои 0–8 проработаны; слои 9–11 (home-manager, deploy, формат) см. в -`docs/arch/invariants.md`. - -## Содержание - -1. [Реестр хостов](#реестр-хостов) -2. [Идентичность и xlib](#идентичность-и-xlib) -3. [Диспетчеризация модулей](#диспетчеризация-модулей) -4. [Пользователь, SSH, секреты](#пользователь-ssh-секреты) -5. [Хранилище](#хранилище) -6. [Сеть и firewall](#сеть-и-firewall) -7. [Сервисы](#сервисы) -8. [Неотвеченные вопросы](#неотвеченные-вопросы) - ---- - -## Реестр хостов - -Единственная точка добавления/изменения хоста — `configurations/default.nix:13-39`. -Имя атрибута **равно** hostname; отдельное `hostname = …` только у `default` -(где attr = `default`). - -| Attr | hostname | device.type | description | -|---|---|---|---| -| `default` | nixos | minimal | Шаблон, hostname `"nixos"`, `device = "minimal"` | -| `atoridu` | atoridu | primary | Основной десктоп, `xanmod` | -| `rydiwo` | rydiwo | secondary | Chuwi MiniBook, `xanmod`, NTFS-том `lamet-drive` | -| `otrecа` | otreca | vds | VPS, SSH только через Tailscale, `grub` без EFI | -| `sapphira` | sapphira | server | Домашний сервер, `firewall.enable = false` намеренно | -| `wsl` | wsl | wsl | WSL NixOS на Windows-хосте `vetymae` | -| `epral` | epral | termux | Android (`nix-on-droid`), отдельный путь конфигурации | - -`device.type` ∈ { minimal, primary, secondary, server, vds, wsl, termux }. -Машина `vetymae` (Windows + WSL) фигурирует в `coredns`, `nginx`, `modules/server/systemd.nix`, -но **не** в реестре хостов — это внешний хост, через который заходят на WSL. - -### Per-host summary - -- **`atoridu`** (`primary/mini-pc`): без `nixos-hardware` (мини-ПК). Linux `xanmod_stable`, - `systemd-boot`, EFI. `stateVersion 26.05`. → `configurations/mini-pc.nix`. -- **`rydiwo`** (`secondary/mini-laptop`): `nixos-hardware: chuwi-minibook-x`, xanmod, - `systemd-boot`, EFI. **NTFS-том `xlib.dirs.lamet-drive`** с `mask = "0000"` — - world-readable/writable по дизайну [?]. `stateVersion 26.05`. -- **`otrecа`** (`vds`): qemu-guest, GRUB без EFI, `disko` + `hardware/vds.nix`. - `firewall.enable = true` + ручной `nftables.ruleset` без финального правила. - `firewall.interfaces.tailscale0.allowedTCPPorts = [22]`. `stateVersion 25.05`. -- **`sapphira`** (`server`): systemd-boot, EFI, ext4 на UUID `37e53ebc-…-a8de`. - bind-mount `/mnt/services` ← `/home/oqyude/External/Services`. `stateVersion 25.05`. -- **`wsl`**: `nixos-wsl` + NixOS-стек, IPv6 on, `firewall.enable = false`. - `stateVersion 24.11`. Реальный Windows-хост — `vetymae`, `192.168.1.100`. -- **`epral`** (`mobile.nix`): не NixOS, **nix-on-droid**. `stateVersion 24.05`. - ---- - -## Идентичность и xlib - -`lib/xlib/` собирает чистые данные (без модулей): - -``` -xlib = { - device = { hostname, type, username, uid, gid }; - isDesktop, isHeadless; # ← от device.type через devices..{desktop,headless} - dirs = mkDirs username; # ← well-known пути, зависят только от username - helpers = { mkBindMount, mkSystemdBind, mkServiceStorage, mkNtfsMount, - mkExfatMount, mkTmpDirs, mkSymlinks }; -} -``` - -- **`mkXlib`** (`lib/xlib/default.nix:38-77`) — единственная точка сборки; вызывается - в `configurations/default.nix:50`. Прокидывается в каждый модуль как - `xlib = …` через `lib/mkSystem.nix:specialArgs`. -- **`devices`** (`lib/xlib/device.nix:12-41`) — закрытое множество device.types. - Неизвестный тип → throw со списком валидных. Добавление типа = новая папка - `modules//` + `home/.nix` + запись в `devices`. -- **`uid/gid`** зашиты как `?` `1000`/`1000` в `mkXlib`. Менять = инвентаризация - во всех хостах, иначе расходятся владельцы файлов на NTFS/exFAT. -- **`sapphira`** — исключение: `users.nix:66` ставит `uid = 1001` для сохранения - совместимости со старым `uid-map` (`yuyus` = 1000). `TODO: delete once - sapphira migrated to 1000`. Цена: exFAT на sapphira получает `uid=1000` от - `xlib.device.uid`, поэтому пользователь не может писать в `/mnt/archive` и - `/mnt/mobile` до миграции. - ---- - -## Диспетчеризация модулей - -### `nixosModules.default` (`modules/default.nix:9-39`) - -Импортирует на **каждый** NixOS-хост (включая `minimal`): - -``` -./essentials → packages, services, settings, ssh, shell, systemd-routines -./options.nix → host.builder.*, host."3x-ui".* -./users.nix → пользователь, sops-секреты -home-manager.nixosModules.home-manager -sops-nix.nixosModules.sops -justray.nixosModules.default -disko.nixosModules.disko -grub2-themes.nixosModules.default -self.homeConfigurations.default.nixosModule -``` - -Плюс `lib.optional xlib.isDesktop ./desktop` (primary, secondary). -Плюс `lib.optional (!isDesktop && type != "minimal") (./. + "/${type}")` -(server, vds, wsl, termux). **termux** попадает сюда только в path nix-on-droid, -не как NixOS-хост (см. `mobile.nix`). - -### `nixosModules.strict` (`modules/default.nix:40-53`) - -Используется только `mobile.nix:22`. Импортирует `options.nix` + -`./`; **всё** остальное NixOS-специфичное (essentials, users, -home-manager, sops, disko, grub2-themes) **выключено**, потому что nix-on-droid -не имеет `services.*`, `users.*`, `sops.*`, `disko.*` в своей модульной системе. - -### Правило для кросс-модульных опций - -Опция живёт в `modules/options.nix`, если её **устанавливает** один модуль, -а **читает** другой. `host.reader.X.enable` живёт в `essentials/ssh.nix`, потому -что его объявляет и использует один модуль. - ---- - -## Пользователь, SSH, секреты - -### Пользователь `oqyude` - -- `uid` = `1000` на всех хостах, кроме `sapphira` (=`1001`, см. выше). -- `home = /home/oqyude`, `homeMode = "700"`. -- `linger = true` на всех хостах — user-services (opencode-web) переживают logout. - Следствие: user-сервисы стартуют и потребляют ресурсы без активной сессии. -- `extraGroups`: `audio disk gamemode networkmanager pipewire wheel libvirtd qemu-libvirtd`. - -### SSH - -- `essentials/ssh.nix`: `services.openssh` включается через `host.ssh.enable`, - `PermitRootLogin = "yes"` (намеренно для deploy), `PasswordAuthentication = false`, - hostKey = `/etc/ssh/id_ed25519`. -- `authorizedKeys` для `oqyude` зашит в `users.nix:87` (`ssh-ed25519 AAAA…`). - Чей — `[?]` (см. вопрос 4.1). -- `users.nix` определяет `root`-authorizedKeys **отсутствует** [?] — root как-то - попадает на хост; deploy-rs использует `sshUser = "oqyude", user = "root"`. - -### Циклическая зависимость ключа - -`/etc/ssh/id_ed25519` одновременно: -- `hostKeys` для sshd (`essentials/ssh.nix:22`) -- `sops.age.sshKeyPaths` для расшифровки (`users.nix:95-97`) -- цель `ssh_key_private_known` (`users.nix:147-152`) -- цель `ssh_key_public_host` (`users.nix:159`) - -Как разворачивается на чистой машине — **одноразовый bootstrap** [?]. -Должен быть задокументирован, иначе при переустановке хоста агент не выведет. - -### `.sops.yaml` - -- Один age-ключ (`*default`), `path_regex: secrets/[^/]+\.(yaml|json|env|ini)$`. -- Покрывает только плоские файлы в `secrets/` (без подкаталогов). -- Добавление секрета = `secrets/<имя>.` строго в корне. -- Дополнительные секреты dotenv/json — через `mkUserSecret` (`users.nix:33-41`). - -### Инвентарь секретов (`users.nix:100-162`) - -| Секрет | Формат | Назначение | -|---|---|---| -| `hashed_password` | yaml | Пароль пользователя | -| `age_key_private` | yaml | `~/.config/sops/age/keys.txt` | -| `opencode_server` | dotenv | `~/.config/opencode/server.env` | -| `opencode_auth` | json | `~/.local/share/opencode/auth.json` (`key=""`) | -| `opencode_account` | json | `~/.local/share/opencode/account.json` (`key=""`) | -| `ssh_key_private` | yaml | `~/.ssh/id_ed25519` | -| `ssh_key_public` | yaml | `~/.ssh/id_ed25519.pub` | -| `ssh_key_private_root` | yaml | `/root/.ssh/id_ed25519` | -| `ssh_key_public_root` | yaml | `/root/.ssh/id_ed25519.pub` | -| `ssh_key_public_host` | yaml | `/etc/ssh/id_ed25519.pub` | - ---- - -## Хранилище - -### `/home/oqyude/External` (ext4) - -- `sapphira`: UUID `37e53ebc-5343-a94d-9fe2-0ca39e13a8de`, fsType `ext4`, - **без `nofail`**, **без automount** — обычный mount, без `x-systemd.automount`, - не помечен как `requiredBy local-fs.target` явно, но NixOS добавляет это для - всех `fileSystems` без `nofail` [?]. -- `rydiwo`: не смонтирован (у ноутбука есть только NTFS `lamet-drive`). -- На других NixOS-хостах — не заявлен (нет внешнего диска). - -### `/mnt/services` (bind) - -- `server.nix:49-52`: `mkBindMount` от `xlib.dirs.services-folder` - (= `/home/oqyude/External/Services`) к `/mnt/services`, `bind,nofail`. -- `server.nix:61-63`: tmpfiles `z /mnt/services 0777 root root`. -- `vds/default.nix:23`: tmpfiles создаёт `/mnt/services` с правами `0755`. -- Используется сервисами на sapphira для bind-mount сервисных данных - (`mkServiceStorage`) и как прямой `stateDir` для gitea/memos/calibre-web/ - immich/nextcloud/step-ca/trilium/uptime-kuma/3x-ui/tape-rotation. - -### `/mnt/archive`, `/mnt/mobile`, `/mnt/lamet`, `/mnt/therima`, `/mnt/vetymae`, `/mnt/soptur` - -- `archive` и `mobile` смонтированы на sapphira через `mkExfatMount` - (`nofail`+uid=1000). -- `lamet` — NTFS на rydiwo (`mask = "0000"`). -- `therima`, `vetymae`, `soptur` — **не** смонтированы нигде в репозитории - (см. вопрос 2.2). -- `dirs.nix` объявляет их все; `dirs.nix` **не** читать как список дисков этой - системы — там имена, часть из которых не существует. - -### Потребители External-диска и порядок защиты - -Включённые на sapphira сервисы с данными на `/mnt/services` или `/home/oqyude/External`: - -- `postgresql`, `samba-smbd`, `homebox` (+setup), `gitea` (+dump), - `navidrome`, `syncthing`, `uptime-kuma`, `immich-server` (+ML), - `nextcloud`, `calibre-web`, `podman-3xui_app`, `podman-tape-rotation` - -Все они обязаны иметь guard на `requiresMountsFor` (задача **B1** в `todo.md`). -Сейчас guard есть **только** у rsync-юнитов (`modules/server/systemd.nix:14,36`), -которые используют `--delete` и потенциально самые опасные при отсутствующем -диске. - ---- - -## Сеть и firewall - -### Топология - -``` -Интернет (роутер, белый IP) - ├── router NAT/proxy → sapphira: 443, 80, 22000, 8443, 22 (5 портов) - │ - └── otreca (VPS): SSH только через Tailscale, не пробрасываем - -LAN (192.168.1.0/24) - ├── 192.168.1.20 = sapphira (домашний сервер) - ├── 192.168.1.1 = роутер (gateway) - ├── 192.168.1.100 = vetymae (Windows-хост; на нём — WSL NixOS = `wsl`) - ├── 192.168.1.101, .102 = соседние машины (rsync/таблица в `termux.nix`) - └── ... - -Tailscale (CGNAT 100.64.0.0/10) - ├── 100.64.0.0 = sapphira (назначен вручную) - ├── 100.64.1.0 = ещё один узел [?] - ├── 100.86.62.4 = opencode на vetymae - └── 100.106.21.39 = miniflux на другом узле -``` - -`192.168.1.20` зашит в ~30 местах: `modules/server/{nginx,coredns,nfs,open-webui}.nix`, -`configurations/*`. `100.64.0.0` — в `nginx.nix`, `nextcloud.nix`, -`modules/vds/{nginx,systemd}.nix`. - -### DNS (`modules/server/coredns.nix`) - -Зоны `zeroq.su` (~17 записей) и `home.arpa` (~17) определены вручную. -Дублируют инвентарь сервисов: добавление сервиса = правка `coredns.nix` + -`nginx.nix` + самого модуля. - -### Firewall - -| Хост | `firewall.enable` | Фильтрация | -|---|---|---| -| sapphira | **false** (намеренно) | Роутер пробрасывает 5 портов: **443, 80, 22000, 8443, 22** | -| otreca | true | Самописанный nftables **без финального правила** → неявный accept; `firewall.interfaces.tailscale0.allowedTCPPorts = [22]` | -| wsl | false | WSL — не сетевой периметр | -| rydiwo, atoridu | default | `desktop` правила | - -Следствия: -- На `sapphira` `openFirewall`/`allowedTCPPorts` не имеют эффекта. -- `nginx.nix:225` (`allowedTCPPorts = [80 443]`) — **мёртвое** правило. -- Допустимо `0.0.0.0` на любом сервисе sapphira — он не открывается в интернет - без проброса на роутере. -- На `otrecа` ruleset требует финальной политики (задача A3). - -### SSH - -`otreca` достижима только через Tailscale: `services.openssh.openFirewall = false`, -`firewall.interfaces.tailscale0.allowedTCPPorts = [22]`. Но при `nftables.enable` -с ручным ruleset это правило может не дойти до файрвола — проверить -`nft list ruleset` на otreca до правок (задача A3). - ---- - -## Сервисы - -### Системные (sapphira, в `modules/server/default.nix:imports`) - -Сервисы в `imports` + `state` + `roles`: - -| Сервис | Файл | Порт | Данные | Guard? | -|---|---|---|---|---| -| acme (Let's Encrypt) | `modules/server/acme.nix` | — | `/var/lib/acme` | — | -| bentopdf | `bentopdf.nix` | — | — | — | -| builder (remote) | `builder.nix` | — | — | — (опция выключена) | -| calibre-web | `calibre-web.nix` | 8083 | `services-mnt-folder/calibre-web(-library)` | нужен B1 | -| chrony | `chrony.nix` | — | — | — | -| coredns | `coredns.nix` | 53 | inline zone | — | -| gitea | `gitea.nix` | 3000 | `services-mnt-folder/gitea` | нужен B1 | -| glances | `glances.nix` | — | — | — | -| homebox | `homebox.nix` | 7745 | `mkServiceStorage` | нужен B1 | -| immich | `immich.nix` | 2283 | `services-mnt-folder/immich` | нужен B1 | -| miniflux | `miniflux.nix` | 6061 | — | — | -| navidrome | `navidrome.nix` | 4533 | `server-home/Music` | нужен B1 | -| nextcloud | `nextcloud.nix` | 10000 | `services-mnt-folder/nextcloud` | нужен B1 | -| nginx | `nginx.nix` | 80/443 | proxy-only | — | -| nix-serve | `nix-serve.nix` | 5000 | — | — | -| onlyoffice | `onlyoffice.nix` | (через nginx) | — | — | -| postgresql | `postgresql.nix` | (local) | `mkServiceStorage` | нужен B1 | -| power | `power.nix` | — | — | — | -| samba | `samba.nix` | ? | `mkServiceStorage` | нужен B1 | -| syncthing | `syncthing.nix` | 8384 (gui), 22000 (data) | `server-home`, `storage/persist/...` | нужен B1 | -| systemd (rsync oneshots) | `systemd.nix` | — | источник/приёмник — оба на External | **уже есть guard** | -| uptime-kuma | `uptime-kuma.nix` | 4001 | `services-mnt-folder/uptime-kuma` | нужен B1 | - -Закомментированы в `imports` (всё ещё живой код, потенциальный шум): -`remnawave, coturn, mealie, memos, minecraft, n8n, netdata, nfs, open-webui, -rsync, step-ca, stirling-pdf, transmission, trilium, zerotier` — см. задачу **E3**. - -### Контейнеры (`modules/containers/`) - -| Контейнер | Файл | Данные | Примечание | -|---|---|---|---| -| 3x-ui | `3x-ui.nix` | `services-nodes-folder//3x-ui/{db,cert}` | **Заморожен**, см. ниже | -| tape-rotation | `tape-rotation.nix` | `services-nodes-folder//tape-rotation` | — | -| remnawave | `remnawave.nix` | `/mnt/services/containers/remnawave` | **закомментирован** в `server/default.nix` | -| remnanode | `remnanode.nix` | `/mnt/services/containers/remnanode` | — | -| kokoro-tts | `kokoro-tts.nix` | — | — | -| openhands | `openhands.nix` | — | — | -| remnawave-examples | `remnawave-examples/*.nix` | docker-compose | шаблоны | - -### 3x-ui — замороженное состояние - -Образ: `ghcr.io/mhsanaei/3x-ui:latest` (**не запинен**). Ядро Xray — на 26.7.x, -миграция на 26.9.x провалена. Панель может обновиться из upstream — поэтому: -- `podman-update-3xui_app` (`3x-ui.nix:80-90`) с `podman pull …:latest` - + `systemctl restart` — **таймер закомментирован**. -- `podman.autoPrune.flags = ["--all"]` (`3x-ui.nix:45-47`) — потенциальный риск: - авто-prune может смести панель без коммита в репозиторий. - -`reality443Forwarding = true` (`modules/vds/default.nix:19`) — следствие отката -`c8d4a12`; смысл утрачен, см. задачу **C5**. - -### Nginx (`modules/server/nginx.nix`) - -~12 vhost'ов через `mkProxy` для обратного проксирования сервисов на 192.168.1.20. -Плюс несколько hand-written: - -- `nextcloud.private` — слушает на `100.64.0.0:10000` (= Tailscale sapphira), - `192.168.1.20:10000`, `127.0.0.1:10000`. -- `office.zeroq.su` — проксирует на nextcloud onlyoffice. -- `pdf.private` — слушает `0.0.0.0:80`, `100.64.0.0:8446`, `192.168.1.20:8446`, - `127.0.0.1:8446` (для Nextcloud PDF). -- `x.zeroq.su` — 3x-ui controller panel + `/subs/`, `/subsjs/`, `/clash/`. -- `zeroq.su` — корневой, заглушка + `/guest/` → LAN `:80`. -- `vetymae.opencodes.zeroq.su` → `100.86.62.4:4096`. -- `lamet.opencodes.zeroq.su` → `100.106.21.39:6061` — **порт miniflux**; либо - ошибка, либо так задумано [?] (см. вопрос 8.2). -- `opencode.zeroq.su` → `127.0.0.1:4096` (opencode-web на самом sapпира). -- `nextcloud.zeroq.su` → `192.168.1.20:10000`, `/whiteboard` → `:3002`. - -`networking.firewall.allowedTCPPorts = [80 443]` (строка 225) — **мёртвое** правило -при `firewall.enable = false`. - ---- - -## Неотвеченные вопросы - -Слои 9–11 (home-manager, deploy, формат) оставлены для прочтения в -`docs/arch/invariants.md`. Неотвеченные вопросы слоёв 1–8: - -| ID | Вопрос | -|---|---| -| 2.2 | `vetymae` / `lamet` / `therima` / `soptur` — те же машины или хосты вне репозитория? | -| 2.5 | `stateVersion` дрейфует 24.05 / 24.11 / 25.05 / 26.05 — намеренно? | -| 2.6 | Есть ли escape hatch для per-host отличий в `xlib`? | -| 3.2 | `any.nix` (minimal) действительно нуждается в home-manager + sops + disko? | -| 4.1 | Как root получает доступ по SSH — `authorizedKeys` для root в коде нет | -| 4.2 | Как разрешается цикл «ключ в секрете, а нужен для расшифровки»? | -| 4.3 | Все файлы в `secrets/` покрыты `path_regex`? | -| 4.4 | Как подключается вторая машина / второй человек при одном age-ключе? | -| 4.5 | `users.nix:87` — личный ключ или общий «ключ от деплоя»? | -| 5.1 | `/mnt/services` в режиме 0777 — осознанно? | -| 5.3 | NFS выключен, Samba работает — миграция? | -| 5.4 | NTFS-том `lamet-drive` с `mask = "0000"` — что на нём лежит? | -| 5.5 | `therima` / `vetymae` / `soptur` — несуществующие остатки или сетевые шары? | -| 5.6 | Где бэкапы БД и 3x-ui? | -| 6.6 | `192.168.1.20` зашит в 30 мест — считаем константой? | -| 6.7 | DNS дублирует инвентарь сервисов — как проверяем рассинхрон? | -| 6.8 | Публичные IP и SSH-алиасы в `home/termux.nix` — карта «хост → адреса» нужна? | -| 6.9 | Какой путь REALITY считается правильным? (→ C5) | -| 7.4 | Почему не публиковать весь диапазон 14380-15379? | -| 8.2 | `lamet.opencodes` → `:6061` — ошибка или так задумано? | -| 8.4 | `onlyoffice` — работает после трёх регрессов? | -| 8.5 | Что слушает `:3002` (`/whiteboard` в nextcloud)? | \ No newline at end of file diff --git a/docs/arch/todo.md b/docs/arch/todo.md deleted file mode 100644 index af9a5c3..0000000 --- a/docs/arch/todo.md +++ /dev/null @@ -1,316 +0,0 @@ -# TODO: правки и инварианты - -Источник: `docs/arch/invariants.md`. Ответы владельца от 2026-10-05 учтены. -Подтверждённые факты зафиксированы в `AGENTS.md` (корень) и `docs/arch/map.md`; -этот файл — только **незакрытые правки и неотвеченные вопросы**. -Порядок: A → B → C → D, потом E (документация для агента). - -Обозначения: `[ ]` не начато, `[x]` сделано, `[!]` блокирует остальное. - ---- - -## Подтверждено (зафиксировано в `AGENTS.md` / `map.md`) - -Эти инварианты уже учтены в ядре и карте — при правке кода опираться на -зафиксированные формулировки. - -- **6.1** Явная финальная политика nftables на VDS → `todo A3` ещё открыто, - но сам «надо запилить» закреплён. -- **6.3** Firewall на sapphira выключен намеренно (граница — роутер, 5 портов: - 22, 80, 443, 8443, 22000) → формулировка в `AGENTS.md §3`, `todo D1`. -- **6.5** `100.64.0.0` = Tailscale-адрес sapphira (назначен вручную) → `AGENTS.md §5`, - `map.md §Сеть и firewall`. -- **7.1 / 7.2** 3x-ui заморожен: панель на latest, ядро Xray на 26.7.x, - миграция 26.9 провалена → `AGENTS.md §4`, `todo C1–C5`. - ---- - -## Ловушки для агента: выглядит сломанным, но это намеренно - -Прежде чем чинить — проверить этот список. Здесь лежат решения, которые -иначе «поправляются» обратно и ломают рабочую систему. - -| Где | Что выглядит ошибкой | На самом деле | -|---|---|---| -| `configurations/server.nix:130` | `networking.firewall.enable = false` на сервере с 20 сервисами на `0.0.0.0` | Намеренно: фильтр на роутере, он пробрасывает 5 портов (см. D1) | -| `configurations/mobile.nix:95`, `wsl.nix:59` | `stateVersion` 24.05 / 24.11 против 26.05 у остальных | Каждый хост зафиксирован на своей версии; не «подровнять» | -| `modules/users.nix:66` | `uid = if hostname == "sapphira" then 1001 else …` с пометкой TODO | Осознанный костыль под старый uid 1000 = `yuyus`; удалять только после миграции ФС | -| `modules/containers/3x-ui.nix:54` | `image = …:latest` | Панель намеренно на последней версии; **ядро** Xray — на 26.7.x, миграция на 26.9 провалена | -| `modules/containers/3x-ui.nix:33-35` | `reality443Forwarding = true` на VDS при откате nginx-stream | Следствие отката `c8d4a12`; смысл утрачен, но опция объявлена — см. C5 | -| `modules/server/default.nix:33-47` | 15 закомментированных модулей с живым кодом | Отключены осознанно; см. E3 | -| `modules/server/{mealie,memos,n8n,netdata,nfs,open-webui,rsync,step-ca,transmission,trilium,zerotier}.nix` | Агент насчитает лишние порты и каталоги | Модули вне `imports` = мёртвый код | -| `home/modules/opencode.nix:339` | `systemd.user.services.opencode-web.Service` вместо привычного `serviceConfig` | `serviceConfig` рендерится в секцию `[serviceConfig]`, которую systemd **молча игнорирует** (`c73a698`) | -| `configurations/vds.nix:73-91` | nftables без финального правила | Известный пробел,см. A3 — **не** «случайно потерялось» | -| `100.64.0.0` в `nginx.nix`, `nextcloud.nix`, `vds/*` | Первый адрес CGNAT `/10`, похож на сетевой | Tailscale-адрес sapphira, назначен вручную | -| `server.nix:61-63` | `z /mnt/services 0777` | World-writable точка монтирования; см. B1 | - ---- - -## A. Блокеры: сломано или не защищено - -### [ ] A1. `mobile.nix` импортирует несуществующий файл - -**Где:** `configurations/mobile.nix:12` -```nix -xlib = import ../lib/xlib.nix { lib = inputs.nixpkgs.lib; }; -``` -Файла `lib/xlib.nix` нет — есть каталог `lib/xlib/` с `default.nix`. -**Правка** (как в `configurations/default.nix:5`): -```nix -xlib = import ../lib/xlib { inherit lib; }; -``` -**Следствие:** до правки `nixOnDroidConfigurations.epral` и `.default` -не вычисляются. Устройство `epral` мертво. -**Проверка:** -``` -nix eval --raw .#nixOnDroidConfigurations.epral.config.environment.etcBackupExtension # ожидается .bak -``` - -### [ ] A2. Убедиться, что `nix flake check` вообще запускается - -**Где:** нет CI; `checks` в `deploy/default.nix:27-29` покрывают только deploy. -**Сначала проверить**, ловит ли текущий `nix flake check` поломку из A1: -``` -nix flake check -``` -Ожидание, которое надо подтвердить: он **уже падает** на `epral`, то есть -проверка существует, но её не запускали. Если падает — A1 и был бы замечен. -**Проверка после A1:** та же команда должна стать зелёной. -**Затем** (E2) — превратить в привычку: прогонять перед каждым коммитом. - -### [ ] A3. Явная финальная политика nftables на VDS - -**Где:** `configurations/vds.nix:73-91` -**Сначала диагностика на otreca** (без неё править опасно — можно отрезать SSH): -``` -nft list ruleset -systemctl status nftables firewall-nftables -``` -Нужно понять, кто реально владеет набором правил: `nftables.enable = true` с -собственным ruleset **и** `networking.firewall.*` включены одновременно -(инвариант 6.2). Затем — править **один** механизм, не оба. -**Что должно получиться** (политика — на выбор владельца, два варианта): -``` -# Вариант «белый список» (предпочтительно): -chain input { - type filter hook input priority 0; policy drop; - iif lo accept - ct state established,related accept - iif "tailscale0" accept - tcp dport { 80, 443 } ct state new limit rate 20/second burst 40 packets accept - tcp dport { 22 } ct state new accept # только если 22 нужен на ens3 -} -# Вариант «мягкий» (минимум изменений, фиксирует текущее поведение): -chain input { - type filter hook input priority 0; - iif lo accept - ct state established,related accept - tcp dport { 80, 443 } ct state new limit rate 20/second burst 40 packets accept - tcp dport { 80, 443 } ct state new drop - # финал accept — но ТОЛЬКО как явно помеченное «разрешено всё остальное»: - iif "ens3" accept comment "PROVISIONAL: explicit allow-all, см. A3" -} -``` -**Инвариант к записи:** последнее правило самописной цепочки всегда явное. -**Проверка:** `nft list chain inet filter input` + `ssh` с внешнего адреса. - ---- - -## B. Защита данных - -### [ ] B1. Guard на несмонтированный носитель `/mnt/services` - -**Где:** `lib/xlib/helpers.nix` (`mkServiceStorage`), потребители — -`modules/server/{postgresql,n8n,samba,homebox,minecraft}.nix` + `modules/containers/3x-ui.nix` -**Проблема (подтверждена владельцем как не продуманная):** `mkServiceStorage` -даёт `bind,x-systemd.automount,nofail`. Если диск `External` (`xlib.dirs.server-home`, -ext4 по UUID, `configurations/server.nix:55-58`) не смонтирован, то `/mnt/services` -— обычный каталог, `/var/lib/` пуст, и сервис **молча** стартует на чистой -базе. Пользователь увидит «потерялись данные». -**Решение (рекомендую):** добавить в `xlib/helpers.nix` -```nix -mkStorageGuard = - { dir }: - { - # сервис не стартует, пока /mnt/services не смонтирован: - # Requires+After на mnt-services.mount, который упадёт, если нет источника - requiresMountsFor = [ dir ]; - }; -``` -и в каждом потребителе: -```nix -systemd.services.postgresql = xlib.helpers.mkStorageGuard { dir = xlib.dirs.services-mnt-folder; }; -``` -**Важно — не проверять `ConditionPathIsMountPoint=/mnt/services`:** bind-mount -внутри одной ФС не меняет `st_dev`, условие вернёт false даже при корректном -монтировании. Надёжны `requiresMountsFor` или `ConditionPathIsMountPoint` на -`xlib.dirs.server-home` (там `st_dev` действительно другой). -**Плюс операционная строка в `AGENTS.md`:** перед рестартом этих сервисов — -`findmnt /mnt/services`. -**Проверка (имитация отказа):** -``` -systemctl stop postgresql -sudo umount /mnt/services # или остановить automount -systemctl start postgresql # ожидается FAIL, а не пустая база -``` - -### [ ] B2. Зафиксировать, что бэкапов в конфигурации нет - -**Где:** `modules/server/postgresql.nix:23` (`postgresqlBackup.enable` закомментирован), -бэкап-сервиса в репозитории нет вообще; БД 3x-ui — sqlite на том же диске. -**Задача — не код, а запись:** в `AGENTS.md` и `invariants.md` явно сказать, -что бэкапы ведутся вне Nix. Иначе агент считает конфиг самодостаточным. -**Ждёт ответа:** где бэкапы и как их проверять (инвариант 5.6). - ---- - -## C. 3x-ui: заморозить рабочее состояние - -### [ ] C1. Вернуть расследование, потерянное при откате - -**Где:** 200 строк удалены коммитом `22a19be`. -**Восстановить и дополнить выводом:** -``` -git show 9974784:modules/containers/3x-ui-migration-notes.md > docs/arch/notes/3x-ui-xray-26.9.md -``` -Дописать в конец: вердикт — миграция ядра 26.7 → 26.9 **провалена**, откат на -рабочее состояние (панель последняя, ядро 26.7.x), обходные скрипты отключены -осознанно; причина отказа — обязательный постквантовый обмен X25519MLKEM768, -ломающий старых клиентов. -**Инвариант:** откат кода не удаляет расследование; заметка живёт в -`docs/arch/notes/`, а не рядом с откатываемым файлом. - -### [ ] C2. Зафиксировать фактические версии панели и ядра - -**Где:** `modules/containers/3x-ui.nix:54` -**Сначала узнать, что реально работает** (на sapphira и на otreca): -``` -podman images --format '{{.Repository}}:{{.Tag}} {{.Id}} {{.Created}}' | grep 3x-ui -podman inspect ghcr.io/mhsanaei/3x-ui --format '{{index .RepoDigests 0}}' -podman exec 3xui_app /app/bin/xray-linux-amd64 version -``` -**Потом** заменить `:latest` на найденный тег (или digest) в коде. -**Инвариант:** образы контейнеров запинены; `latest` запрещён — обновление -образа это правка в коде, а не `podman pull` на хосте. -**Почему срочно:** `podman.autoPrune.flags = ["--all"]` (`3x-ui.nix:45-47`) + -`:latest` = рабочее состояние может смениться без единого коммита. - -### [ ] C3. Убрать сервис автообновления 3x-ui - -**Где:** `modules/containers/3x-ui.nix:80-90` (`podman-update-3xui_app` с -`podman pull … :latest`) и закомментированный таймер (строка 97-103). -**Предложение:** удалить сервис целиком, оставив комментарий-предупреждение. -Обновление панели через `pull` — ровно тот путь, которым в 2026-10-04 -декларация разошлась с рантаймом; автоматизировать его нельзя. -**Инвариант:** ни один контейнер в этом репозитории не обновляется сам. - -### [ ] C4. Записать в AGENTS.md, что ядро Xray — состояние панели, а не Nix - -Версия ядра выбирается в UI панели и лежит в её sqlite-БД, то есть **вне** Nix. -Репозиторий не может её гарантировать. -**Операционное правило:** перед деплоем/рестартом 3x-ui проверять версию ядра -в панели; обновление ядра = отдельная задача с записью в -`docs/arch/notes/`, а не молчаливый `podman pull`. - -### [ ] C5. Решить судьбу `reality443Forwarding` - -**Где:** `modules/vds/default.nix:19` (`= true`), `modules/options.nix:66-75`, -`modules/containers/3x-ui.nix:33-35`. -Состояние после отката `c8d4a12`: опция включена, поэтому на otreca -пробрасывается `127.0.0.1:15380:443`, тогда как единственный Reality-инбаунд -контейнера слушает 8443, а публичный 8443 проброшен напрямую (`0.0.0.0:8443`). -Потребителя потока (nginx-stream) откат убрал. -**Варианты:** (а) оставить как есть и описать в инвариантах; (б) погасить опцию -в `vds/default.nix` и убрать её из `options.nix`; (в) довести до рабочего -состояния. **Ждёт решения** — связано с 6.9. - ---- - -## D. Сетевая граница: записать то, чего нет в репозитории - -### [ ] D1. Пробросы роутера — главный недостающий инвариант - -Ответ владельца: на сервер пробрасываются **443, 80, 22000 (syncthing), -8443 (xray), 22 (ssh)**. Это **настоящая граница доверия**, и она живёт -в конфиге роутера, то есть вне репозитория. -**Записать в двух местах:** `docs/arch/invariants.md` (слой 6) и `AGENTS.md`. -Формулировка инварианта: -> Экспозиция наружу определяется пробросами на роутере, не `openFirewall`. -> На `sapphira` `networking.firewall.enable = false` намеренно. -> Список пробросов: 22, 80, 443, 8443 (3x-ui/Xray REALITY), 22000 (syncthing). -> Новый сервис не становится доступен из интернета, пока не добавлен проброс. -> `networking.firewall.*` на `sapphira` не имеет эффекта. - -### [ ] D2. Зафиксировать `100.64.0.0` как Tailscale-адрес sapphira - -Моё прежнее замечание («сеть вместо адреса») было неверным — адрес назначен -вручную. Записать как факт + список из 4 мест, которые придётся править при -смене: `modules/server/nginx.nix`, `modules/server/nextcloud.nix`, -`modules/vds/systemd.nix`, `modules/vds/nginx.nix`. -**Опционально (отложено):** вынести `192.168.1.20` в `xlib.dirs` — сейчас -зашит в ~30 местах в 6 файлах. Не срочно, это рефакторинг. - -### [ ] D3. Убрать мёртвое правило firewall - -**Где:** `modules/server/nginx.nix:225-228` — `allowedTCPPorts = [80 443]` -не действует при `firewall.enable = false` (`server.nix:130`). -Удалить или пометить комментарием «депенит от D1». - ---- - -## E. Документация для агента (после прохода по invariants.md) - -### [ ] E1. Написать `AGENTS.md` в корне -Собирается из подтверждённых инвариантов. Структура: карта хостов → -что где лежит → инварианты (нарушишь = сломает) → ловушки из таблицы выше → -команды проверки. Ожидаемый бюджет — до 150 строк. - -### [ ] E2. Выбрать проверки, которые заменят половину инвариантов -Кандидаты из инварианта 11.2: -1. ни одного `:latest` в образах (grep по `image =`); -2. `nix flake check` зелёный — уже ловит A1; -3. домены в `coredns.nix` ↔ vhost'ы в `nginx.nix` совпадают в обе стороны; -4. для каждого потребителя `mkServiceStorage` каталог существует на `External`; -5. последнее правило самописной nftables-цепочки явное; -6. `listen.addr` — адрес интерфейса, а не сеть; -7. все файлы в `secrets/` матчат `path_regex` из `.sops.yaml`. -**Ждёт ответа:** какие из них делать, какие — избыточны. - -### [ ] E3. Судьба 15 закомментированных модулей -`modules/server/default.nix:33-47` — `remnawave, coturn, mealie, memos, -minecraft, n8n, netdata, nfs, open-webui, rsync, step-ca, stirling-pdf, -transmission, trilium, zerotier`. Удалить или оставить как референс? -Они мешают агенту насчитывать порты и каталоги, которых нет. - ---- - -## F. Ждут ответа (блокируют E1) - -Индексы в `docs/arch/invariants.md`: - -| № | Вопрос, который блокирует запись инварианта | -|---|---| -| 2.2 | `vetymae` / `lamet` / `therima` / `soptur` — это те же машины или хосты вне репозитория? | -| 2.5 | `stateVersion` дрейфует 24.05 / 24.11 / 25.05 / 26.05 — намеренно? | -| 2.6 | Есть ли escape hatch для per-host отличий в `xlib`, или «у всех хостов одно» — закон? | -| 3.2 | `any.nix` (minimal) действительно нуждается в home-manager + sops + disko? | -| 4.1 | Как root получает доступ по SSH — `authorizedKeys` для root в коде нет | -| 4.2 | Как разрешается цикл «ключ `/etc/ssh/id_ed25519` лежит внутри секрета, а нужен для расшифровки» | -| 4.3 | Что лежит в `secrets/`, все ли файлы покрыты `path_regex` | -| 4.4 | Как подключается вторая машина / второй человек при одном age-ключе | -| 4.5 | `users.nix:87` — личный ключ или общий «ключ от деплоя» | -| 5.1 | `/mnt/services` в режиме 0777 — осознанно? | -| 5.3 | NFS выключен, Samba работает — миграция? | -| 5.4 | NTFS-том `lamet-drive` с `mask = "0000"` — что на нём лежит | -| 5.5 | `therima` / `vetymae` / `soptur` — несуществующие остатки или сетевые шары | -| 5.6 | Где бэкапы БД и 3x-ui (→ B2) | -| 6.6 | `192.168.1.20` зашит в 30 мест — считаем константой? | -| 6.7 | DNS дублирует инвентарь сервисов — как проверяем рассинхрон | -| 6.8 | Публичные IP и SSH-алиасы в `home/termux.nix` — карта «хост → адреса» нужна? | -| 6.9 | Какой путь REALITY считается правильным (→ C5) | -| 7.4 | Почему не публиковать весь диапазон 14380-15379 | -| 8.2 | `lamet.opencodes` → `:6061` — это miniflux; ошибка или так задумано | -| 8.4 | `onlyoffice` — работает после трёх регрессов? | -| 8.5 | Что слушает `:3002` (`/whiteboard` в nextcloud) | -| 9.2 | Кто создаёт `~/Music` и `~/Storage` при `createDirectories = false` | -| 10.1 | Почему `deploy-rs` не деплоит `atoridu`, `wsl`, `epral` |