metaagent: install v3.0.0, migrate docs/arch/* → .agent/

- 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
This commit is contained in:
2026-10-09 20:37:14 +03:00
parent 7c9aa24779
commit 16644fcc0b
48 changed files with 4389 additions and 916 deletions
+36
View File
@@ -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"
}
+119
View File
@@ -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.<attr>` (хосты) + `nixOnDroidConfigurations.<attr>` (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
│ ├── <host>.nix ← модульное тело хоста
│ └── hardware/<host>.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
│ └── <type>/ ← 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/<name>.(yaml|json|env|ini)
```
**Паттерн:** модульный монолит с xlib-инъекцией (аналог dependency injection через `specialArgs`).
**Ключевые модули:**
| Модуль | Описание |
|---|---|
| `configurations/default.nix:13-39` | Реестр хостов (7 entries); `mkXlib` в строке 50 |
| `configurations/<host>.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/<type>.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.
+107
View File
@@ -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.<attr>` через `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/<type>.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.<name>.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).
@@ -0,0 +1,83 @@
# ADR-0001: sops-пути — только через `config.sops.secrets.<name>.path`
**Статус:** accepted
**Дата:** 2026-10-09
**Контекст:**
sops-nix материализует секреты на `/run/secrets/<attr>` по умолчанию.
Атрибут sops-блока (`config.sops.secrets.<attr>`) — единственный источник
истины для on-disk пути. Любой `path =` override на sops-блоке плюс хардкод
`"/run/secrets/<attr>"` в потребителе делает потребителя **молча**
сломанным: `nixos-rebuild` проходит, сервис стартует, файл читается — но
контент от прошлой версии или пустой. Симптом приходит из рантайма, не из CI.
До этой правки (см. «Обратное») в кодовой базе были хардкоды путей в 5
местах: `authelia.nix`, `open-webui.nix`, `tape-rotation.nix`, `remnawave.nix`.
В `remnawave.nix` тот же риск был двойной: путь хардкожен и в генераторе,
и в контейнере → расхождение двух копий = silent breakage.
**Рассматриваемые альтернативы:**
1. **A. `${config.sops.secrets.<attr>.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.<attr>.path}`.
- **Симметрия с `mkUserSecret`.** `users.nix:33-41` уже использует этот
паттерн (`config.sops.secrets.<name>.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/<name>` — молчаливое расхождение. Защита: код-ревью +
кандидат в CI-проверки (`secrets/missing-paths.nix` или grep).
**Invariant:**
> Любой потребитель sops-секрета в `modules/` ссылается на путь через
> `${config.sops.secrets.<attr>.path}`, а не через литерал
> `"/run/secrets/<attr>"`. Атрибут 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).
+14
View File
@@ -0,0 +1,14 @@
{
"version": "3.0.0",
"updated_at": "2026-10-09T20:30",
"decisions": [
{
"id": "0001",
"title": "sops-пути — только через config.sops.secrets.<name>.path",
"status": "accepted",
"date": "2026-10-09",
"file": ".agent/decisions/0001-sops-secrets-paths.md",
"tags": ["sops", "secrets", "security", "invariant"]
}
]
}
@@ -1,13 +1,9 @@
# Инварианты: вопросы владельцу # Roadmap Sources
Проход по репозиторию сверху вниз, 2026-10-05. 120 `.nix`, ~8.5k строк. Источники задач для фазы DECOMPOSITION. Собрано при проходе по репозиторию
2026-10-05, ответы владельца учтены. Полный Q&A-источник (с разделами «Вопрос»,
Структура: «Факт», «Риск», «Кандидат») восстановим из git-истории:
- Сводный ответ, ядро и ловушки → **`AGENTS.md`** (корень репозитория). `git log -p docs/arch/invariants.md | less` (последний коммит, где Q&A был полным).
- Подробная карта архитектуры с per-host деталями и инвентарём сервисов →
**`docs/arch/map.md`**.
- Этот файл → **открытые вопросы** (слои 0–8) + ещё непрочитанные **слои 9–11**
(home-manager, deploy, формат).
Пометки: `[!]` — найденный дефект, не вопрос. `[?]` — не смог определить по коду. Пометки: `[!]` — найденный дефект, не вопрос. `[?]` — не смог определить по коду.
`[✓]` — отвечено владельцем 2026-10-05. `[✓]` — отвечено владельцем 2026-10-05.
@@ -15,47 +11,33 @@
## Статус ответов (2026-10-05) ## Статус ответов (2026-10-05)
Отвечено: **2.1, 5.2, 6.1, 6.3, 6.5, 7.1, 7.2** (7 пунктов). Остальные ждут Отвечено: **2.1, 5.2, 6.1, 6.3, 6.5, 7.1, 7.2** (7 пунктов). Остальные ждут
ответа (таблица ниже). ответа (таблица ниже). Ключевое из ответов:
Ключевое из ответов, что меняет картину: - **6.5** — `100.64.0.0` не «сетевой адрес вместо интерфейса»: Tailscale-адрес
sapphira, назначен вручную. См. ADR R1.4.
- **6.5 — моя ошибка.** `100.64.0.0` не «сетевой адрес вместо интерфейса»: - **6.3** — `firewall.enable = false` на sapphira не недосмотр: граница на
это Tailscale-адрес sapphira, назначенный вручную. роутере (5 портов). См. ADR R1.3 + задачу D1.
- **6.3 — это не дыра, а осознанное решение.** Граница держится на роутере: - **7.2** — 3x-ui рабочий, откат осознанный. Состояние «заморожено», не сломано.
на сервер пробрасываются ровно 5 портов — **443, 80, 22000 (syncthing), См. ADR R1.5 + задачи C1–C5.
8443 (xray), 22 (ssh)**. `firewall.enable = false` на sapphira — следствие, - **5.2** — подтверждённая дыра в защите данных → задача B1.
а не недосмотр. Проблема в другом: **список пробросов нигде не записан
в репозитории**, и именно его агент обязан уважать (D1 в `todo.md`).
- **7.2 — 3x-ui рабочий.** Откат сделан осознанно: панель последняя, ядро Xray
осталось на 26.7.28, миграция на 26.9 провалена, лишний код закомментирован.
Состояние — «заморожено», а не «сломано».
- **5.2 — подтверждённая дыра в защите данных.** Guard для несмонтированного
носителя не был продуман → задача 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 | | 1 | Все `outputs` флейка вычисляются | A1: правка `lib/xlib.nix` → `lib/xlib`; закрепить через `nix flake check` | A1 |
| 2 | External-диск монтируется до сервисов | mkServiceStorage + bind без guard'а → сервис стартует на пустой БД | todo B1 | | 2 | External-диск монтируется до сервисов | mkServiceStorage + bind без guard'а → сервис стартует на пустой БД | B1 |
| 3 | Сетевая граница sapphira = роутер | 5 портов: 22, 80, 443, 8443, 22000; `firewall.enable = false` намеренно | todo D1 | | 3 | Сетевая граница sapphira = роутер | 5 портов: 22, 80, 443, 8443, 22000; `firewall.enable = false` намеренно | D1 |
| 4 | `100.64.0.0` = Tailscale sapphira | Назначен вручную; в 4 файлах | AGENTS.md §5 | | 4 | `100.64.0.0` = Tailscale sapphira | Назначен вручную; в 4 файлах | D2 |
| 5 | 3x-ui заморожен | Панель на latest; ядро Xray на 26.7.x; миграция 26.9 провалена | todo C1–C5 | | 5 | 3x-ui заморожен | Панель на latest; ядро Xray на 26.7.x; миграция 26.9 провалена | C1–C5 |
| 6 | nftables на VDS — явная финальная политика | Сейчас ruleset без финального правила + конфликт с `firewall.*` | todo A3 | | 6 | nftables на VDS — явная финальная политика | Сейчас ruleset без финального правила + конфликт с `firewall.*` | A3 |
| 7 | sops-пути — через `config.sops.secrets.<name>.path` | Любой `path =` override на sops-блоке делает хардкод-потребителя молча сломанным: rebuild зелёный, сервис стартует, контент пустой | этот коммит, см. §S1 | | 7 | sops-пути — через `config.sops.secrets.<name>.path` | Любой `path =` override на sops-блоке делает хардкод-потребителя молча сломанным | ADR-0001 |
## Сводка по ловушкам ## Открытые вопросы (слои 0–8)
Полная таблица (10 пунктов) в **`AGENTS.md`** → раздел «Ловушки». Кратко: Самые важные — выделены. Источник для новых задач в `.agent/tasks/manifest.json`.
`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)
Самые важные — выделены.
| ID | Вопрос | Что блокирует | | ID | Вопрос | Что блокирует |
|---|---|---| |---|---|---|
@@ -91,15 +73,6 @@
| 8.5 | Что слушает `:3002` (`/whiteboard` nextcloud)? | Карта сервисов | | 8.5 | Что слушает `:3002` (`/whiteboard` nextcloud)? | Карта сервисов |
| 8.6 | Бэкапы вне Nix — записать | Документация | | 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. home-manager
**9.1** `home/home.nix:52-57` — для пользователя импортируется **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 нельзя **9.3 [!]** `home/modules/opencode.nix:339-350` (`c73a698`): в home-manager нельзя
писать `serviceConfig = { ... }` — рендерится литеральная секция `[serviceConfig]`, писать `serviceConfig = { ... }` — рендерится литеральная секция `[serviceConfig]`,
которую systemd молча игнорирует («Unknown section 'serviceConfig'. Ignoring.»). которую systemd молча игнорирует. Закреплено в R2.
Правильно: `systemd.user.services.opencode-web.Service = { ... }`.
**Кандидат (готовый инвариант, стоит закрепить буквально в `AGENTS.md`):**
в home-manager cgroup-опции (`MemoryHigh`, `OOMScoreAdjust`, …) пишутся
в `systemd.user.services.<name>.Service`, **не** в `serviceConfig`. Ошибка
не диагностируется — она просто не применяется.
**9.4** `linger = true` добавлен ради `opencode-web` (`users.nix:71-75`) и включён **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), **9.6** Секреты opencode приходят в `~/.config/opencode/server.env` (dotenv),
`~/.local/share/opencode/auth.json` и `account.json` (json, `key = ""`). `~/.local/share/opencode/auth.json` и `account.json` (json, `key = ""`).
**Кандидат:** эти три файла перезаписываются sops при каждой активации — ручные **Кандидат:** эти три файла перезаписываются sops при каждой активации — ручные
правки в них теряются. Уже отражено в комментарии `users.nix:120-131`, стоит правки в них теряются.
закрепить как инвариант.
---
## Слой 10. deploy и проверка ## Слой 10. deploy и проверка
@@ -152,94 +117,45 @@ git-истории файла (последний коммит, где Q&A бы
**Вопрос:** почему не деплоится десктоп? И безопасно ли пересобирать ноутбук **Вопрос:** почему не деплоится десктоп? И безопасно ли пересобирать ноутбук
`rydiwo` по SSH (он может быть выключен/на другом Wi-Fi)? `rydiwo` по SSH (он может быть выключен/на другом Wi-Fi)?
**Кандидат:** `deploy-rs` = только серверы + ноутбук; десктоп и WSL обновляются **Кандидат:** `deploy-rs` = только серверы + ноутбук; десктоп и WSL обновляются
вручную. Инвариант: не добавлять в `deploy.nodes` хост, который нельзя вручную.
пересобрать в любой момент без риска потерять доступ.
**10.2** `deploy/default.nix:18-19` — `sshUser = "oqyude"`, `user = "root"`. **10.2** `deploy/default.nix:18-19` — `sshUser = "oqyude"`, `user = "root"`.
См. 4.1: root-доход по SSH не описан в конфигурации. См. 4.1: root-доход по SSH не описан в конфигурации.
**Кандидат:** деплой требует ручной настройки root-доступа на каждом из 3 хостов —
это скрытая зависимость, которую агент не выведет.
**10.3** `deploy/default.nix:27-29` — `checks = builtins.mapAttrs (... deployChecks)`. **10.3** `deploy/default.nix:27-29` — `checks = builtins.mapAttrs (... deployChecks)`.
**Вопрос:** `nix flake check` реально проходит сейчас? Учитывая 2.1 (`lib/xlib.nix`) **Вопрос:** `nix flake check` реально проходит сейчас?
он должен падать на `nixOnDroidConfigurations`. Падает или `checks` покрывают
не всё дерево outputs?
**Кандидат (первое, что стоит сделать):** добиться, чтобы
`nix flake check` был зелёным — это единственная автоматическая защита от
подобных breakage'ов.
**10.4** CI нет, `flake check` не запускается автоматически. **10.4** CI нет, `flake check` не запускается автоматически.
**Кандидат:** минимальный локальный набор перед коммитом: **Кандидат:** минимальный локальный набор перед коммитом:
`nix flake check && nix build .#nixosConfigurations.<хост>.config.system.build.toplevel --dry-run`. `nix flake check && nix build .#nixosConfigurations.<хост>.config.system.build.toplevel --dry-run`.
--- ## Слой 11. Формат
## Слой 11. Формат (то, что я предлагаю зафиксировать как процесс)
**11.1** Где будет жить итог: `AGENTS.md` в корне (читается агентом всегда), **11.1** Где будет жить итог: `AGENTS.md` в корне (читается агентом всегда),
`docs/arch/map.md` (карта хостов/сервисов), `docs/arch/invariants.md` (этот файл). `docs/arch/map.md` (карта хостов/сервисов), `docs/arch/invariants.md` (этот файл).
**Кандидат:** этот файл после ответов превращается в `docs/arch/invariants.md` **Кандидат:** этот файл после ответов превращается в `.agent/roadmap/sources.md`
с колонкой «ответ» и становится источником для `AGENTS.md`; `AGENTS.md` — краткая с колонкой «ответ» и становится источником для `.agent/context/project-state.md`;
выжимка, без подробностей. `.agent/context/project-state.md` — сжатая выжимка, без подробностей. (Сделано.)
**11.2** Какие инварианты можно превратить в автоматическую проверку (тогда они **11.2** Какие инварианты можно превратить в автоматическую проверку (тогда они
перестанут «забываться»): перестанут «забываться»): 7 кандидатов в `analysis-report.md §5`.
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`.
**Вопрос:** какие из этих проверок ты хочешь, а какие — лишний CI? **Вопрос:** какие из этих проверок ты хочешь, а какие — лишний CI?
---
## Шаблон инварианта ## Шаблон инварианта
Этот шаблон — для добавления новых инвариантов в этот документ Этот шаблон — для добавления новых инвариантов в `.agent/rules/project-rules.md`
(и для зеркалирования в `AGENTS.md`). Та же 4-осевая структура (и для зеркалирования в `AGENTS.md`). Та же 4-осевая структура используется,
используется, чтобы вытащить «невидимое знание владельца» из чтобы вытащить «невидимое знание владельца» из существующего кода в явное
существующего кода в явное утверждение. утверждение.
1. **Утверждение** — что именно верно и нельзя менять без осознанного 1. **Утверждение** — что именно верно и нельзя менять без осознанного
решения. Один-два абзаца, никаких «может быть». решения. Один-два абзаца, никаких «может быть».
2. **Где** — конкретные файлы и строки. Агент не должен угадывать. 2. **Где** — конкретные файлы и строки. Агент не должен угадывать.
3. **Почему** — что происходит при нарушении. Лучше всего — сценарий 3. **Почему** — что происходит при нарушении. Лучше всего — сценарий
(rebuild / рантайм), а не абстрактный риск. (rebuild / рантайм), а не абстрактный риск.
4. **Действие** — `todo X.Y`, ссылка на коммит, или явное 4. **Действие** — task id в `manifest.json`, ссылка на коммит, или явное
«закреплено автоматической проверкой (см. §11.2)». «закреплено автоматической проверкой (см. analysis-report.md §5)».
Дополнительные поля по необходимости: «ловушка» (выглядит сломанным, Дополнительные поля по необходимости: «ловушка» (выглядит сломанным,
намеренно), «обратное» (где это уже было сломано раньше), намеренно), «обратное» (где это уже было сломано раньше), «как проверить»
«как проверить» (grep / CI). (grep / CI).
### S1 — sops-пути: `config.sops.secrets.<name>.path`
- **Утверждение.** Любой потребитель sops-секрета в `modules/` ссылается
на путь через `${config.sops.secrets.<attr>.path}`, а не через
литерал `"/run/secrets/<attr>"`. Атрибут 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/<attr>`
по умолчанию, но `sops.secrets.<attr>.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` — литеральный
хардкод.
+160
View File
@@ -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.<name>.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.<name>.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/<host>.nix` + `configurations/{hardware,disko}/<host>.nix` |
| Добавить системный сервис | `modules/server/<name>.nix`, добавить в `modules/server/default.nix:imports` |
| Добавить home-пакет для пользователя | `home/<device_type>.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/<name>.<yaml\|json\|env\|ini>`; `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.<host>.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/<type>/` через
`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/<type>.nix` = единственный источник «что есть на этом хосте» для
пользователя; добавление пакета в новый тип = правильный файл, а не
`home/default.nix`.
- `.sops.yaml`: один age-ключ (`*default`), `path_regex: secrets/[^/]+\.(yaml|json|env|ini)$`.
Покрывает только плоские файлы в `secrets/` (без подкаталогов). Дополнительные
секреты dotenv/json — через `mkUserSecret` (`users.nix:33-41`).
+45
View File
@@ -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. **Непонятно, какую команду вызвать** — спросить пользователя, не угадывать.
+87
View File
@@ -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`.
+95
View File
@@ -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** — если хочется явно зафиксировать альтернативу до решения.
+85
View File
@@ -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** — если альтернатива снимает/добавляет риски.
+68
View File
@@ -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** — некоторые инварианты рождаются из рисков.
+104
View File
@@ -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** — для систематизации рисков.
+80
View File
@@ -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** — некоторые риски закрываются через принятое решение.
+205
View File
@@ -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": "<uuid>",
"target_repo": "<path>",
"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": "<timestamp>"
}
```
Секции `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.
+128
View File
@@ -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": "<uuid>",
"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": "<timestamp>"
}
```
Поля `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`
+95
View File
@@ -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": "<timestamp>"
}
```
## Ветвление
| 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` обновлён
+106
View File
@@ -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` обновлён
+142
View File
@@ -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` обновлён
+117
View File
@@ -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": "<timestamp>"
}
```
## Выход
- `.agent/tasks/manifest.json`
- `.agent/tasks/manifest.md`
- Обновлённый `checkpoints.json`
## Критерии завершения
- [ ] Цель разбита на атомарные задачи
- [ ] У каждой задачи — acceptance criteria, origin, files
- [ ] Зависимости корректны (нет циклов)
- [ ] Задачи сверены с roadmap (если `sources.md` существует)
- [ ] `manifest.json` и `manifest.md` созданы
- [ ] `checkpoints.json` обновлён
+136
View File
@@ -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
+145
View File
@@ -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": "<timestamp>",
"tasks": [{ "id": "T1", "title": "...", "archived_at": "<timestamp>" }],
"requests": [{ "id": "req-T1", "task_id": "T1", "archived_at": "<timestamp>" }],
"checkpoints": [{ "file": "checkpoints/<ts>.json", "archived_at": "<timestamp>" }]
}
```
### 6.7. Создать handoff-summary
Создать `.agent/handoff-summary.md` — полная сводка для следующего агента:
```markdown
## Session Summary
**Session:** <id>
**Goal:** <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": "<timestamp>" }
```
## Выход
- `.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` финализирован
+113
View File
@@ -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:** <id>
**MetaAgent version:** 3.0.0
**Date:** <timestamp>
**Goal:** <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": "<timestamp>" }
```
### 7.6. Сигнал
```
HANDOFF COMPLETE
Session: <session_id>
Target: <target_repo>
Type: <existing | greenfield | scaffold>
Tasks: <N> total, <M> completed, <K> pending
Следующий агент начинает с .agent/handoff-summary.md
```
## Выход
- `.agent/session-summary.md`
- Финальный `.agent/checkpoints.json`
- (если METASTATE не было) `.agent/archive/index.json`
## Критерии завершения
- [ ] Все артефакты на месте
- [ ] (если METASTATE не было) `completed` задачи архивированы
- [ ] `session-summary.md` создан
- [ ] `checkpoints.json` финализирован
- [ ] Сигнал отправлен пользователю
+23
View File
@@ -0,0 +1,23 @@
# ADR-NNNN: <Заголовок решения>
**Статус:** proposed | accepted | deprecated | superseded
**Дата:** {{ date }}
**Контекст:** почему возникла необходимость в решении, какая проблема решается.
**Рассматриваемые альтернативы:**
1. Вариант A — описание
2. Вариант B — описание
3. Вариант C — описание
**Решение:** выбран вариант <A/B/C>.
**Обоснование:** почему выбран именно этот вариант (критерии: сложность, поддерживаемость, производительность, совместимость).
**Последствия:**
- Позитивные: ...
- Негативные: ...
- Риски: ...
**Invariant (если применимо):** ключевое правило, которое не должен нарушать исполнительный агент. Если можно — ссылка на тест, проверяющий invariant.
+86
View File
@@ -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 }}
+80
View File
@@ -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 }}
+57
View File
@@ -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` — состояние фаз и список задач.
+17
View File
@@ -0,0 +1,17 @@
# Project Rules
Правила, которым агент обязан следовать во всех фазах.
Добавляйте сюда условия, которые должны соблюдаться всегда — они будут прочитаны
перед началом каждой фазы и учтены при декомпозиции и реализации.
## Обязательные правила
- (укажите правила, например: «Всегда использовать tabs для отступов»)
## Запреты
- (укажите запреты, например: «Не трогать CI/CD конфигурацию»)
## Конвенции проекта
- (укажите конвенции, например: «Имена классов в PascalCase, функции в snake_case»)
+43
View File
@@ -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 }}
+29
View File
@@ -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 }}"
]
}
+7
View File
@@ -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 | ... | ... | ... | ... |
+42
View File
@@ -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 }}
@@ -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
}
@@ -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
}
@@ -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
}
+50
View File
@@ -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`
+24
View File
@@ -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"
}
]
}
+42
View File
@@ -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 }}
...
+1
View File
@@ -0,0 +1 @@
3.0.0
+240
View File
@@ -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` не создаются.
+365
View File
@@ -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 <target>/.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
}
+312
View File
@@ -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 <<EOF
Usage: $0 [--check|--update] [target_path]
Install MetaAgent sources into <target>/.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
+272
View File
@@ -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) ждут ответа владельца и станут задачами после ответа."
}
+86
View File
@@ -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
```
Остальные задачи можно делать параллельно.
+2 -1
View File
@@ -1,4 +1,5 @@
.vscode .vscode
.omo .omo
__pycache__ __pycache__
scripts scripts
.temp
+78 -77
View File
@@ -2,97 +2,90 @@
aliases: [] aliases: []
cssclasses: cssclasses:
date-created: 2026-10-09T19:01 date-created: 2026-10-09T19:01
date-modified: 2026-10-09T20:23 date-modified: 2026-10-09T20:30
tags: [] tags: [metaagent, nixos, agents]
--- ---
# AGENTS.md # 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 секунд) ## Архитектура (30 секунд)
``` ```
flake.nix flake.nix
├── configurations/ ← реестр хостов (1 запись = 1 машина) ├── configurations/ ← реестр хостов (1 запись = 1 машина)
│ ├── default.nix ← hosts + xlibLib + mkSystem ├── modules/ ← essentials + per-type (desktop/server/vds/wsl/containers/termux)
│ ├── <host>.nix ← модульное тело хоста
│ └── hardware/<host>.nix
├── home/ ← home-manager (per device-type) ├── home/ ← home-manager (per device-type)
├── modules/ ├── lib/xlib/ ← чистые данные: devices, dirs, helpers
│ ├── options.nix ← кросс-модульные опции ├── deploy/, secrets/ (sops), overlays/, pkgs/
│ ├── default.nix ← defaultModule + strictModule (для nix-on-droid) └── .agent/ ← MetaAgent state (rules, decisions, tasks, context, requests, roadmap)
│ ├── 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/<name>.(yaml|json|env|ini)
``` ```
`xlib` (в `lib/xlib/`) — чистые данные: identity (`device`), capability flags, Подробная карта: `.agent/context/project-state.md` и `.agent/context/analysis-report.md`.
директории, helper'ы. Передаётся в каждый модуль через `specialArgs`. Конфиг не
может переопределить `xlib` — единственная точка изменения это `configurations/default.nix`.
## Хосты ## Хосты
| Attr / имя | device.type | Роль | Деплой | Примечание | | Attr / имя | device.type | Роль | Деплой | stateVersion | Примечание |
|---|---|---|---|---| |---|---|---|---|---|---|
| `default` (nixos) | minimal | Шаблон / минималка | — | hostname `"nixos"` | | `default` (nixos) | minimal | Шаблон / минималка | — | — | hostname `"nixos"` |
| `atoridu` | primary | Основной десктоп | — | xanmod | | `atoridu` | primary | Основной десктоп | — (manual) | 26.05 | xanmod, mini-PC |
| `rydiwo` | secondary | Ноутбук Chuwi MiniBook (xanmod, NTFS) | deploy-rs | `stateVersion 26.05` | | `rydiwo` | secondary | Chuwi MiniBook | deploy-rs | 26.05 | xanmod, NTFS `lamet-drive` |
| `otrecа` | vds | VPS, SSH только по Tailscale | deploy-rs | nftables, DHCP, no firewall в NixOS | | `otrecа` | vds | VPS | deploy-rs | 25.05 | Tailscale-only SSH, nftables (см. T3) |
| `sapphira` | server | Домашний сервер (белый IP через роутер) | deploy-rs | `firewall.enable = false` намеренно | | `sapphira` | server | Домашний сервер | deploy-rs | 25.05 | `firewall.enable = false` намеренно |
| `wsl` | wsl | WSL NixOS на vetymae | — | nixos-wsl module | | `wsl` | wsl | WSL NixOS | — (manual) | 24.11 | на vetymae (Windows 192.168.1.100) |
| `epral` | termux | Android (`nix-on-droid`) | — | через `mobile.nix`, отдельный модульный путь | | `epral` | termux | Android | — | 24.05 | nix-on-droid, через `mobile.nix` |
`device.type` ∈ { minimal, primary, secondary, server, vds, wsl, termux }.
`modules/defaultModule` импортирует `modules/<type>/` через `lib.optional
(!isDesktop && type != "minimal") (./. + "/${type}")`.
## Подтверждённые инварианты ## Подтверждённые инварианты
1. **Все `outputs` флейка должны вычисляться.** `configurations/mobile.nix:12` > Полные формулировки (с «Где» и «Почему») — в `.agent/rules/project-rules.md` (R1).
импортировал несуществующий `lib/xlib.nix` — был сломан, `epral` не
собирался. Зафиксировать через `nix flake check`. 1. **Все `outputs` флейка должны вычисляться.** `configurations/mobile.nix:12` импортировал несуществующий `lib/xlib.nix` — был сломан, `epral` не собирался. → задача T1.
2. **Носитель данных (`/home/oqyude/External`) обязан быть смонтирован** до 2. **External-диск обязан быть смонтирован** до старта `postgresql`, `n8n`, `samba`, `homebox`, `minecraft`, `3x-ui`, `tape-rotation`. → задача T4.
старта `postgresql`, `n8n`, `samba`, `homebox`, `minecraft`, `3x-ui`, 3. **Сетевая граница sapphira — роутер.** 5 портов: **443, 80, 22000 (syncthing), 8443 (xray), 22 (ssh)**. `firewall.enable = false` намеренно. → задача T11.
`tape-rotation`. `mkServiceStorage` даёт `bind,x-systemd.automount,nofail` 4. **`100.64.0.0` = Tailscale-адрес sapphira**, назначен вручную. В 4 файлах. → задача T12.
— без guard'а сервис стартует на пустой БД. → todo B1. 5. **3x-ui заморожен.** Панель на `:latest`, ядро Xray на 26.7.x. Миграция на 26.9.x провалена. → задачи T6–T10.
3. **Сетевая граница sapphira — роутер.** `firewall.enable = false` намеренно. 6. **nftables на VDS требует явной финальной политики.** Текущий ruleset — без финального правила → неявный accept. → задача T3.
Роутер пробрасывает ровно 5 портов: **443, 80, 22000 (syncthing), 8443 7. **sops-пути — через `config.sops.secrets.<name>.path`.** Любой `path =` override на sops-блоке делает хардкод-потребителя молча сломанным. → ADR-0001.
(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'ом, перед правкой.
## Ловушки (выглядит сломанным, намеренно) ## Ловушки (выглядит сломанным, намеренно)
| Где | Что выглядит ошибкой | На самом деле | | Где | Что выглядит ошибкой | На самом деле |
|---|---|---| |---|---|---|
| `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 | Каждый хост зафиксирован на своей версии | | `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`; удалять только после миграции ФС | | `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:54` | `image = …:latest` | Панель намеренно latest; ядро Xray — на 26.7.x |
| `3x-ui.nix:33-35` | `reality443Forwarding = true` на VDS | Следствие отката `c8d4a12`; смысл утрачен, см. todo C5 | | `3x-ui.nix:33-35` | `reality443Forwarding = true` на VDS | Следствие отката `c8d4a12`; см. задачу T10 |
| `server/default.nix:33-47` | 15 закомментированных модулей | Отключены осознанно, см. todo E3 | | `server/default.nix:33-47` | 15 закомментированных модулей | Отключены осознанно, см. задачу T16 |
| `opencode.nix:339` | `systemd.user.services.opencode-web.Service` | `serviceConfig` рендерится в секцию `[serviceConfig]`, systemd молча игнорирует (`c73a698`) | | `opencode.nix:339` | `systemd.user.services.opencode-web.Service` | `serviceConfig` рендерится в секцию `[serviceConfig]`, systemd молча игнорирует; см. R2 |
| `vds.nix:73-91` | nftables без финального правила | Известный пробел, см. todo A3 | | `vds.nix:73-91` | nftables без финального правила | Известный пробел, см. задачу T3 |
| `100.64.0.0` | Первый адрес CGNAT `/10` | Tassigned вручную, см. §5 | | `100.64.0.0` | Первый адрес CGNAT `/10` | Tailscale-адрес sapphira, см. инв. 4 |
| `server.nix:61-63` | `z /mnt/services 0777` | World-writable точка монтирования; см. todo B1 | | `server.nix:61-63` | `z /mnt/services 0777` | World-writable точка монтирования; см. задачу T4 |
## Куда лезть по задаче ## Куда лезть по задаче
@@ -100,11 +93,11 @@ flake.nix
|---|---| |---|---|
| Добавить хост | `configurations/default.nix` + `configurations/<host>.nix` + `configurations/{hardware,disko}/<host>.nix` | | Добавить хост | `configurations/default.nix` + `configurations/<host>.nix` + `configurations/{hardware,disko}/<host>.nix` |
| Добавить системный сервис | `modules/server/<name>.nix`, добавить в `modules/server/default.nix:imports` | | Добавить системный сервис | `modules/server/<name>.nix`, добавить в `modules/server/default.nix:imports` |
| Добавить home-пакет для пользователя | `home/<device_type>.nix` (через `lib.mkIf` или просто список) | | Добавить home-пакет | `home/<device_type>.nix` |
| Добавить опцию, читаемую несколькими модулями | `modules/options.nix` | | Добавить кросс-модульную опцию | `modules/options.nix` |
| Изменить mount/имя пользователя | `lib/xlib/dirs.nix`, `lib/xlib/device.nix` | | Изменить mount/имя пользователя | `lib/xlib/dirs.nix`, `lib/xlib/device.nix` |
| Изменить домен / сертификат | `modules/server/coredns.nix` + `modules/server/nginx.nix` (или `vds/`) | | Изменить домен / сертификат | `modules/server/coredns.nix` + `modules/server/nginx.nix` (или `vds/`) |
| Sops-секрет | положить в `secrets/<name>.<yaml|json|env|ini>`; `users.nix:99` уже подключает `secrets/default.yaml`; dotenv/json-секреты — через `mkUserSecret` | | Sops-секрет | `secrets/<name>.<yaml\|json\|env\|ini>`; `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 /home/oqyude/External
findmnt /mnt/services findmnt /mnt/services
# state of guard-зависимостей (когда будет todo B1)
systemctl show postgresql -p Requires -p After | tr ' ' '\n' | grep -E 'mnt-|home-oqyude'
# sops # sops
sops --version sops --version
``` ```
@@ -132,13 +122,24 @@ sops --version
## Где НЕ лезть без ответа владельца ## Где НЕ лезть без ответа владельца
- `secrets/` (sops-encrypted, расшифровываются `/etc/ssh/id_ed25519` → циклический bootstrap). - `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 детали, сетевая топология, - `/adr` — записать архитектурное решение
инвентарь сервисов, все известные open questions. - `/red-team` — попытаться сломать дизайн
- `docs/arch/invariants.md` — слои 9–11 (home-manager, deploy, формат) + - `/risk-register` — зафиксировать допущения
полный список неотвеченных вопросов слоёв 1–8. - `/alt-arch` — описать альтернативу
- `docs/arch/todo.md` — задачи A1–F (правки и документирование). - `/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`).
-396
View File
@@ -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.<type>.{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/<type>/` + `home/<type>.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` +
`./<device.type>`; **всё** остальное 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/<имя>.<yaml|json|env|ini>` строго в корне.
- Дополнительные секреты 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/<host>/3x-ui/{db,cert}` | **Заморожен**, см. ниже |
| tape-rotation | `tape-rotation.nix` | `services-nodes-folder/<host>/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)? |
-316
View File
@@ -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/<service>` пуст, и сервис **молча** стартует на чистой
базе. Пользователь увидит «потерялись данные».
**Решение (рекомендую):** добавить в `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` |