9 Commits
Author SHA1 Message Date
oqyude 7443ec059f v1.1.5 2026-07-24 22:45:07 +03:00
oqyude 055edc7750 metaagent integration 2026-07-22 01:38:31 +03:00
oqyude 09069047a6 md update 2026-07-11 14:56:35 +03:00
oqyude 00cc10b90a v1.1.4 2026-07-11 14:46:30 +03:00
oqyude ec75fd9d72 v1.1.3 2026-07-11 14:46:16 +03:00
oqyude 6ff146eaaf review and new module 2026-07-11 14:33:05 +03:00
oqyude 4cf5e9da4a fix with paragraphs 2026-07-11 03:43:26 +03:00
oqyude d546947e61 some automation
Release / release (push) Has been cancelled
2026-07-11 03:26:31 +03:00
oqyude 801fdec71e workflow and fixes
Release / release (push) Has been cancelled
2026-07-11 03:22:18 +03:00
49 changed files with 4227 additions and 192 deletions
+85
View File
@@ -0,0 +1,85 @@
# Analysis Report
## Session
- **Session ID:** a1b2c3d4-e5f6-7890-abcd-ef1234567890
- **Target repo:** S:\Git\zeroq-qol-modules
- **Date:** 2026-07-22
- **Project type:** existing
## 1. Общая информация
- **README:** Obsidian-плагин с модульной архитектурой. Каждая QoL-функция — отдельный модуль, включаемый/отключаемый в настройках. Сборка: `npm run build`.
- **Лицензия:** MIT
- **CI/CD:** GitHub Actions — `release.yml` (сборка и релиз при пуше тега v*)
- **Точка входа:** `src/main.ts` → компилируется в `main.js` через esbuild
- **Система сборки:** npm + esbuild (0.17.x), TypeScript 5.x
## 2. Стек технологий
| Компонент | Значение |
|---|---|
| Язык | TypeScript (5.x) |
| Фреймворк | Obsidian API (plugin) |
| База данных | — |
| Тестовый раннер | нет |
| Пакетный менеджер | npm |
| Линтер/форматтер | нет (не настроен) |
## 3. Архитектура
```
src/
├── main.ts # Plugin class onload/onunload
├── settings.ts # ZeroQSettingTab — единая панель настроек
├── types.ts # QoLModule, ZeroQSettings, ModuleSettings
├── locales/
│ ├── en.ts # Locale interface + English
│ ├── ru.ts # Russian translation
│ └── index.ts # Language auto-detect + export locale
└── modules/
├── index.ts # MODULES registry (static array)
├── base-module.ts # BaseModule abstract class
├── attachment-clean-paste/
│ └── index.ts # Paste/drop handler — ![[embed]] → [[path|name]]
└── preserve-link-aliases/
└── index.ts # Rename handler — preserves aliases in links
```
**Паттерн:** Модульный монолит (Plugin → Module Registry → Modules)
**Ключевые модули:**
| Модуль | Описание |
|---|---|
| `main.ts` | Жизненный цикл плагина: загрузка настроек, bootstrap модулей |
| `settings.ts` | Единая панель настроек: рендерит toggle + inline-настройки для каждого модуля |
| `types.ts` | Интерфейсы: `QoLModule`, `ZeroQSettings`, `ModuleSettings` |
| `locales/` | Локализация: автоопределение языка из `<html lang>`, fallback на en |
| `base-module.ts` | Абстрактный класс с `registerCleanup()` для автоматической очистки |
| `attachment-clean-paste` | Обработка paste/drop: копирует файл в attachments, вставляет `[[path|name]]` |
| `preserve-link-aliases` | Обработка rename: сохраняет алиасы `[[path|oldname]]` при переименовании |
## 4. Конвенции
- **Стиль:** tabs для отступов, kebab-case для файлов, PascalCase для классов
- **Импорты:** `import type` для type-only импортов; абсолютные пути из Obsidian, относительные внутри проекта
- **Типизация:** строгая — `noImplicitAny: true`, `strictNullChecks: true`; интерфейсы для настроек модулей
- **Обработка ошибок:** try/catch в асинхронных операциях, `new Notice()` для user-facing ошибок, `console.error` для логирования
- **Логирование:** `console.error` в исключениях, без отдельного логгера
## 5. Тесты
- **Команда запуска:** нет
- **Всего тестов:** 0
- Тесты отсутствуют в проекте.
## 6. Базовая проверка
- **Сборка:** успешно (`npm run build` — main.js 17.1kb)
- **Запуск:** невозможно (Obsidian-плагин, требуется среда Obsidian)
- **Git status:** чисто (ветка dev, untracked только .agent/)
## 8. Примечания
Существующий проект с 2 модулями. Все модули следуют единому паттерну: BaseModule → onload/onunload/renderInlineSettings. Архитектура спроектирована для лёгкого добавления новых модулей через регистрацию в `MODULES` и добавление переводов. Есть подробная документация по добавлению модуля в `review.md` (197 строк). Проект не имеет тестов, линтера, CI (кроме релизного workflow).
+6
View File
@@ -0,0 +1,6 @@
{
"version": "1.1.0",
"archived_at": "2026-07-22T00:00:00.000Z",
"tasks": [],
"checkpoints": []
}
+39
View File
@@ -0,0 +1,39 @@
{
"metaagent_version": "1.1.0",
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"target_repo": "S:\\Git\\zeroq-qol-modules",
"goal": "Проанализировать существующий Obsidian-плагин zeroq-qol-modules (v1.1.4) и подготовить манифест задач для исполнительного агента.",
"project_type": "existing",
"config": {
"depth": 4,
"design": {
"adr": false,
"alternative_arch": false
},
"red_team": false,
"risk_register": false,
"decomposition": {
"invariant_tests": false
},
"handoff": {
"layer_structure": false
}
},
"phases": {
"analysis": "completed",
"design": "skipped",
"red_team": "skipped",
"decomposition": "completed",
"environment": "completed",
"handoff": "completed"
},
"tasks": [
{ "id": "T1", "title": "Настроить ESLint и Prettier", "type": "config", "status": "pending", "depends_on": [] },
{ "id": "T2", "title": "Написать тесты для BaseModule", "type": "test", "status": "pending", "depends_on": [] },
{ "id": "T3", "title": "Написать тесты для Attachment Clean Paste модуля", "type": "test", "status": "pending", "depends_on": ["T2"] },
{ "id": "T4", "title": "Написать тесты для Preserve Link Aliases модуля", "type": "test", "status": "pending", "depends_on": ["T2"] },
{ "id": "T5", "title": "Добавить CI workflow для тестов и линтинга", "type": "config", "status": "pending", "depends_on": ["T1", "T3", "T4"] }
],
"last_updated": "2026-07-22T00:00:00.000Z",
"handoff_completed": true
}
+96
View File
@@ -0,0 +1,96 @@
# Handoff Summary
## Session Info
- **Session ID:** `a1b2c3d4-e5f6-7890-abcd-ef1234567890`
- **Target Repo:** S:\Git\zeroq-qol-modules
- **Goal:** Проанализировать существующий Obsidian-плагин zeroq-qol-modules (v1.1.4) и подготовить манифест задач для исполнительного агента.
- **Date:** 2026-07-22
- **Depth:** 4 (Light)
- **Config:** analysis=yes, design=skipped(existing), decomposition=yes, setup=yes, handoff=yes
## Repo Summary
Obsidian-плагин с модульной архитектурой. TypeScript, esbuild. 2 модуля: Attachment Clean Paste и Preserve Link Aliases. Система настроек с inline-тогглами. Локализация en/ru. Тесты и линтер отсутствуют.
## Project Type
- **Type:** existing
- **Design report:** — (existing project)
## ADR Summary
Не применимо (depth=4, adr=no)
## Risk Register
Не применимо (depth=4, risk_register=no)
## Environment Status
- **Build:** SUCCESS (esbuild, 17.1kb, 11ms)
- **Tests:** 0 total (тест-раннер не настроен)
- **Baseline log:** `.agent/baseline-test-report.log`
- **Dependencies:** установлены (npm)
## Task Overview
| Status | Count |
|---|---|
| Total | 5 |
| Pending | 5 |
| In Progress | 0 |
| Completed | 0 |
| Failed/Skipped | 0 |
**Task by type:**
- config: 2
- test: 3
## Tasks (ordered)
### T1: Настроить ESLint и Prettier
- Type: config
- Depends on: —
- Files: package.json, .eslintrc.json, .prettierrc.json
- Status: pending
### T2: Написать тесты для BaseModule
- Type: test
- Depends on: —
- Files: src/modules/base-module.ts, tests/modules/base-module.test.ts, package.json, tsconfig.json
- Status: pending
### T3: Написать тесты для Attachment Clean Paste модуля
- Type: test
- Depends on: T2
- Files: src/modules/attachment-clean-paste/index.ts, tests/modules/attachment-clean-paste.test.ts
- Status: pending
### T4: Написать тесты для Preserve Link Aliases модуля
- Type: test
- Depends on: T2
- Files: src/modules/preserve-link-aliases/index.ts, tests/modules/preserve-link-aliases.test.ts
- Status: pending
### T5: Добавить CI workflow для тестов и линтинга
- Type: config
- Depends on: T1, T3, T4
- Files: .github/workflows/ci.yml
- Status: pending
## Next Steps
Исполнительный агент начинает с задачи **T1** (ESLint + Prettier) или **T2** (BaseModule тесты) — обе независимы и могут выполняться параллельно.
## Caveats
- Проект не собирается через tsc --noEmit (4 type errors в коде модулей, ошибки в obsidian.d.ts)
- esbuild-сборка работает корректно
- Рекомендуется начать с T1 (линтер уловит существующие type issues)
- В проекте нет тестового раннера — T2 включает его настройку
## Checkpoints
Файл: `.agent/checkpoints.json`
Актуальное состояние чекпоинтов прилагается.
+30
View File
@@ -0,0 +1,30 @@
# MetaAgent Request
# Auto-generated from user interview on 2026-07-22
## Параметры сессии
| Функция | Вкл | Аргументы |
|---|---|---|
| ANALYSIS | ✓ | — |
| DESIGN | ✗ | (existing project) |
| RED_TEAM | ✗ | — |
| RISK_REGISTER | ✗ | — |
| DECOMPOSITION | ✓ | invariant_tests=no |
| SETUP | ✓ | — |
| HANDOFF | ✓ | layer_structure=no |
## Глубина проработки
**Значение:** 4 (Light)
| Уровень | Название | Описание |
|---|---|---|
| 1-2 | Scaffold | Только структура проекта + пустые модули |
| 3-4 | Light | (default) Быстрый дизайн + задачи без расширений |
| 5-6 | Standard | Полный ANALYSIS→DESIGN→DECOMP→SETUP→HANDOFF |
| 7-8 | Deep | Standard + ADR, Risk Register, Alternative Architecture |
| 9-10 | Maximum | Deep + Red Team Review, Executable Invariants |
## Цель
Проанализировать существующий Obsidian-плагин `zeroq-qol-modules` (v1.1.4) и подготовить манифест задач для исполнительного агента.
+17
View File
@@ -0,0 +1,17 @@
# Project Rules
Правила, которым агент обязан следовать во всех фазах.
Добавляйте сюда условия, которые должны соблюдаться всегда — они будут прочитаны
перед началом каждой фазы и учтены при декомпозиции и реализации.
## Обязательные правила
- (укажите правила, например: «Всегда использовать tabs для отступов»)
## Запреты
- (укажите запреты, например: «Не трогать CI/CD конфигурацию»)
## Конвенции проекта
- (укажите конвенции, например: «Имена классов в PascalCase, функции в snake_case»)
+39
View File
@@ -0,0 +1,39 @@
# BOUNDARIES — Рамки и границы
Что мета-агенту **разрешено**, **запрещено** и в каких случаях **нужно остановиться**.
## Разрешено
| Действие | Примечание |
|---|---|
| Читать любые файлы в целевом репозитории | Все файлы, включая .git, конфиги, историю |
| Создавать/изменять файлы в `.agent/` | Единственная директория для артефактов |
| Устанавливать/обновлять зависимости | Только через штатный пакетный менеджер проекта |
| Изменять конфигурационные файлы | Только если это необходимо для сборки/тестов (например, добавить requirements.txt) |
| Запускать сборку и тесты | Для верификации окружения |
| Читать документацию, issue, PRs | Для понимания контекста |
| Запрашивать уточнения у пользователя | Если не хватает информации для декомпозиции |
| Копировать исходники MetaAgent в `.agent/src/` целевого проекта | Только на фазе INIT, без перезаписи существующих файлов |
| Создавать/обновлять `AGENTS.md` в корне целевого проекта | Только если файла не существует |
| **Обязательно:** читать `.agent/rules/project-rules.md` перед каждой фазой | Исполнение правил пользователя — приоритет выше стандартных протоколов |
| Перемещать завершённые артефакты в `.agent/archive/` | Только на фазе HANDOFF, только для completed/failed артефактов |
## Запрещено
| Действие | Почему |
|---|---|
| Писать production-код | Это работа исполнительного агента |
| Рефакторить существующий код | Мета-агент не меняет логику |
| Удалять файлы | Если файл мешает — нужно сообщить пользователю |
| Коммитить в main/master | Коммиты делает исполнительный агент по задачам |
| Менять удалённые настройки CI/CD | Если CI сломан — сообщить пользователю |
| Пул-реквесты | Исполнительный агент создаёт PR после выполнения задач |
| Модифицировать код, не связанный с задачей | Только то, что нужно для окружения |
## Когда остановиться
1. **Репозиторий не собирается** — сообщить пользователю с логом ошибки, не продолжать
2. **Неясна цель** — запросить уточнение, не гадать
3. **Обнаружены секреты/токены** — не копировать, сообщить пользователю
4. **Цель выходит за рамки одной сессии** — разбить, запросить приоритет
5. **Проект не использует известные технолологии** — запросить у пользователя инструкцию по сборке
+336
View File
@@ -0,0 +1,336 @@
# META_AGENT_GUIDE — Главная инструкция
## Жизненный цикл сессии
```
.agent/metaagent-request.md
INIT → ANALYSE → [DESIGN] → [RED_TEAM] → DECOMPOSITION → SETUP → (CHECKPOINT)* → HANDOFF → EXIT
│ │
▼ ▼
ADR (опц.) Invariant Tasks (опц.)
Alt.Arch (опц.)
Risk Register (опц.)
```
Фазы выполняются **строго последовательно**. Фаза DESIGN — только если project_type = greenfield/scaffold.
Фаза RED_TEAM — только если config.red_team = yes.
Все артефакты размещаются в `.agent/` целевого репозитория (с layer-структурой или плоские, в зависимости от config).
---
## Конфигурация сессии (.agent/metaagent-request.md)
Перед запуском сессии пользователь заполняет `.agent/metaagent-request.md` (см. `TEMPLATES/metaagent-request.md`). Файл должен находиться в директории `.agent/` целевого репозитория.
Ключевые параметры:
### Шкала глубины (depth 1-10)
| Уровень | Название | Что выполняется |
|---|---|---|
| 1-2 | Scaffold | INIT → ANALYSIS → SETUP (только структура, без реализации) |
| 3-4 | Light | + DESIGN (без ADR/альтернатив), DECOMPOSITION (без инвариантов), HANDOFF — **(default)** |
| 5-6 | Standard | полный цикл с базовым DESIGN и DECOMPOSITION |
| 7-8 | Deep | + ADR, Alternative Architecture, Risk Register, Invariant Tests |
| 9-10 | Maximum | + Red Team Review, Executable Invariants для всех ADR |
### Функции (таблица вкл/выкл)
| Функция | Фаза | Глубина | Описание |
|---|---|---|---|
| adr | DESIGN | >=7 | Создание ADR для каждого ключевого решения |
| alternative_arch | DESIGN | >=7 | Обязательное описание альтернативной архитектуры |
| red_team | DESIGN (после) | >=9 | Red Team Review — попытка разрушить архитектуру |
| risk_register | DESIGN | >=7 | Явный реестр допущений |
| invariant_tests | DECOMPOSITION | >=7 | Задачи-инварианты для каждого ADR |
| layer_structure | HANDOFF | любая | Организация .agent/ по слоям (layer-0..3) |
---
## Фаза 0: INIT
**Вход:** целевой репозиторий + опционально `.agent/metaagent-request.md`.
**Протокол:** `PROTOCOLS/00_CONFIG.md`
**Действия:**
- Прочитать `VERSION` — текущая версия MetaAgent
- Склонировать/открыть целевой репозиторий
- Создать директорию `.agent/` в корне целевого репозитория (если нет)
- **Установить исходники MetaAgent в `.agent/src/`:**
- Скопировать `META_AGENT_GUIDE.md`, `BOUNDARIES.md`, `WORKFLOW.md`, `VERSION` в `.agent/src/`
- Скопировать `PROTOCOLS/` и `TEMPLATES/` в `.agent/src/`
- Скопировать `install.sh` и `install.ps1` в `.agent/src/` (для возможности обновления)
- Если файлы уже существуют — пропустить (не перезаписывать)
- **Создать `.agent/rules/`** — директорию для пользовательских правил
- Если `.agent/rules/project-rules.md` не существует — создать из шаблона `.agent/src/TEMPLATES/project-rules.md`
- **Создать/обновить `AGENTS.md` в корне целевого репозитория** (если нет — создать, если есть — не трогать)
- Прочитать `PROTOCOLS/00_CONFIG.md`
- Выполнить 00_CONFIG:
- Если `.agent/metaagent-request.md` существует — прочитать config из него
- Если нет — провести интервью с пользователем (или принять `default`)
- Валидировать config относительно depth
- Если не было файла — создать `.agent/metaagent-request.md` с пометкой Auto-generated
- **Проверить версию:** если `.agent/checkpoints.json` существует → выполнить `PROTOCOLS/00_MIGRATE.md` (сравнить metaagent_version, применить миграцию при необходимости)
- Прочитать `PROTOCOLS/01_ANALYSIS.md`
- Инициализировать `.agent/checkpoints.json` с `metaagent_version` (если не существовал)
```json
{
"metaagent_version": "1.1.0",
"session_id": "<uuid>",
"target_repo": "<path>",
"goal": "<цель от пользователя>",
"project_type": "pending",
"config": {
"depth": 4,
"design": { "adr": false, "alternative_arch": false },
"red_team": false,
"risk_register": false,
"decomposition": { "invariant_tests": false },
"handoff": { "layer_structure": false }
},
"phases": {
"analysis": "pending",
"design": "pending",
"red_team": "pending",
"decomposition": "pending",
"environment": "pending",
"handoff": "pending"
},
"tasks": [],
"last_updated": "<timestamp>"
}
```
**Выход:** готовая `.agent/` + checkpoints.json с metaagent_version и config.
---
## Фаза 1: ANALYSE
**Вход:** целевой репозиторий, `.agent/metaagent-request.md` (или auto-generated), checkpoints.json (analysis: pending, config: from INIT).
**Протокол:** `PROTOCOLS/01_ANALYSIS.md`
**Действия:**
- **Прочитать `.agent/rules/project-rules.md`** — учесть пользовательские правила
- Прочитать config из checkpoints.json (уже получен на INIT через 00_CONFIG)
- Если config отсутствует — применить default config (depth=4) как fallback
- Выполнить анализ репозитория по протоколу (определяет тип проекта)
- Записать результат в `.agent/analysis-report.md`
- Обновить checkpoints.json: `phases.analysis = "completed"`, `project_type = "existing" | "greenfield" | "scaffold"`
**Выход:** `.agent/analysis-report.md`
**Ветвление:**
- `project_type = "greenfield"` или `"scaffold"` → далее фаза DESIGN
- `project_type = "existing"` → DESIGN пропускается, сразу DECOMPOSITION
---
## Фаза 2: DESIGN (условная)
**Вход:** analysis-report.md, checkpoints.json (analysis: completed, project_type: greenfield/scaffold).
**Протокол:** `PROTOCOLS/02_DESIGN.md`
**Действия:**
- **Прочитать `.agent/rules/project-rules.md`** — учесть пользовательские правила
- Спроектировать архитектуру, модули, данные, интерфейсы
- Если config.design.alternative_arch: описать альтернативную архитектуру
- Если config.design.adr: создать ADR для каждого ключевого решения → `.agent/layer-1/adr/`
- Если config.risk_register: создать `.agent/layer-1/risk-register.md`
- Записать результат в `.agent/design-report.md`
- Обновить checkpoints.json: `phases.design = "completed"`
**Ветвление:**
- Если config.red_team = yes → следующая фаза RED_TEAM
- Иначе → сразу DECOMPOSITION
**Выход:** `.agent/design-report.md`, опционально `.agent/layer-1/adr/*.md`, `.agent/layer-1/risk-register.md`
---
## Фаза 2b: RED_TEAM (опциональная)
**Вход:** design-report.md, ADR (опционально), checkpoints.json (design: completed).
**Протокол:** `PROTOCOLS/02b_REDTEAM.md`
**Действия:**
- **Прочитать `.agent/rules/project-rules.md`** — учесть пользовательские правила
- Выполнить Red Team Review по протоколу
- Записать результат в `.agent/layer-1/red-team-report.md`
- Дополнить risk-register.md (если существует)
- Если найдены критические проблемы — исправить design-report
- Обновить checkpoints.json: `phases.red_team = "completed"`
**Выход:** `.agent/layer-1/red-team-report.md`
---
## Фаза 3: DECOMPOSITION
**Вход:** analysis-report.md + design-report.md (опционально) + ADR (опционально) + checkpoints.json.
**Протокол:** `PROTOCOLS/03_DECOMPOSITION.md`
**Действия:**
- **Прочитать `.agent/rules/project-rules.md`** — учесть пользовательские правила
- Разбить цель (и дизайн) на атомарные задачи
- Если config.decomposition.invariant_tests: создать задачи-инварианты для каждого ADR
- Записать манифест в `.agent/task-manifest.json` и `.agent/task-manifest.md`
- Обновить checkpoints.json: `phases.decomposition = "completed"`, заполнить `tasks`
**Выход:** `.agent/task-manifest.json`, `.agent/task-manifest.md`
---
## Фаза 4: SETUP
**Вход:** analysis-report.md, design-report.md (опционально), task-manifest.json, checkpoints.json (decomposition: completed).
**Протокол:** `PROTOCOLS/04_ENVIRONMENT_SETUP.md`
**Действия:**
- **Прочитать `.agent/rules/project-rules.md`** — учесть пользовательские правила
- Выполнить настройку окружения по протоколу (ветка A для existing, ветка B для greenfield)
- Записать результат проверки в `.agent/baseline-test-report.log` и `.agent/setup-report.log`
- Обновить checkpoints.json: `phases.environment = "completed"`
**Выход:** рабочее окружение + `.agent/baseline-test-report.log`
---
## Фаза 5: CHECKPOINT (сквозная)
**Вход:** любая фаза.
**Протокол:** обновлять checkpoints.json после каждого значимого шага.
**Архивирование перед сохранением чекпоинта:**
- Если checkpoints.json уже существует — сохранить предыдущую версию в `.agent/archive/checkpoints/<last_updated>.json`
**Формат:**
```json
{
"metaagent_version": "1.1.0",
"session_id": "<uuid>",
"target_repo": "<path>",
"goal": "<цель>",
"project_type": "existing | greenfield | scaffold",
"config": {
"depth": 6,
"design": { "adr": true, "alternative_arch": true },
"red_team": false,
"risk_register": false,
"decomposition": { "invariant_tests": true },
"handoff": { "layer_structure": true }
},
"phases": {
"analysis": "completed",
"design": "completed",
"red_team": "skipped",
"decomposition": "in_progress",
"environment": "pending",
"handoff": "pending"
},
"tasks": [
{ "id": "T1", "title": "...", "status": "completed",
"depends_on": [], "acceptance_criteria": ["..."] },
{ "id": "T2", "title": "...", "status": "pending",
"depends_on": ["T1"], "acceptance_criteria": ["..."] }
],
"last_updated": "<timestamp>"
}
```
`status` может быть: `pending`, `in_progress`, `completed`, `failed`, `skipped`.
Фаза `red_team` может быть `skipped` если config.red_team = false.
---
## Фаза 6: HANDOFF
**Вход:** все предыдущие фазы completed.
**Протокол:** `PROTOCOLS/05_HANDOFF.md`
**Действия:**
- **Прочитать `.agent/rules/project-rules.md`** — учесть пользовательские правила
- **Архивировать завершённые задачи:**
- Для каждой задачи со статусом `completed` в `task-manifest.json`:
- Перенести полное описание в `.agent/archive/tasks/<id>.json`
- Заменить в манифесте на one-liner: `{ "id": "<id>", "title": "<title>", "status": "archived" }`
- Создать `.agent/archive/index.json` со списком архивированных задач
- Заархивировать предыдущий `checkpoints.json` в `.agent/archive/checkpoints/`
- Выполнить валидацию всех артефактов
- Если config.handoff.layer_structure: организовать `.agent/` по слоям
- Записать `.agent/handoff-summary.md` (в layer-3 при layer_structure=yes)
- Создать `.agent/session-summary.md` (в layer-0 при layer_structure=yes)
- Обновить checkpoints.json: `phases.handoff = "completed"`
- Сообщить пользователю/оркестратору
**Выход:** `.agent/handoff-summary.md` — итоговый документ для исполнительного агента.
---
## Фаза 7: EXIT
Мета-агент завершает работу. Управление переходит к исполнительному агенту.
---
## Структура .agent/
.agent/ всегда содержит служебную директорию `src/` с исходниками MetaAgent (см. фазу INIT).
При layer_structure=yes артефакты сессии раскладываются по слоям layer-0..3.
```
.agent/
src/ # исходники MetaAgent (всегда)
META_AGENT_GUIDE.md # главная инструкция
PROTOCOLS/ # протоколы фаз
TEMPLATES/ # шаблоны артефактов
BOUNDARIES.md # границы
WORKFLOW.md # примеры работы
VERSION # версия MetaAgent
install.sh # скрипт установки/обновления (Unix)
install.ps1 # скрипт установки/обновления (Windows)
rules/ # пользовательские правила (всегда)
project-rules.md # правила проекта — читать перед каждой фазой
archive/ # архив завершённых артефактов (создаётся при HANDOFF)
index.json # мета-индекс архива
tasks/ # детали завершённых задач
checkpoints/ # исторические чекпоинты
adr/ # заменённые ADR
reports/ # устаревшие отчёты
layer-0/ # ядро сессии (только при layer_structure=yes)
checkpoints.json # всегда (ядро)
session-summary.md # краткая сводка сессии
layer-1/ # архитектурные решения (справочно)
adr/
001-технологический-стек.md
002-архитектурный-паттерн.md
...
risk-register.md
red-team-report.md
layer-2/ # дизайн и анализ (справочно)
analysis-report.md
design-report.md
layer-3/ # состояние исполнения
handoff-summary.md
task-manifest.json
task-manifest.md
baseline-test-report.log
setup-report.log
```
Исполнительный агент всегда начинает с layer-0 (checkpoints + session-summary),
затем при необходимости обращается к layer-1 (ADR для понимания "почему"),
layer-2 (детали дизайна), layer-3 (что было сделано).
+145
View File
@@ -0,0 +1,145 @@
# Протокол 00: Конфигурация сессии (CONFIG)
## Цель
Определить параметры сессии MetaAgent: глубину проработки, набор функций, тип проекта. Выполняется на фазе INIT.
## Вход
- `VERSION` — текущая версия MetaAgent
- Запрос пользователя (цель)
- Опционально: `.agent/metaagent-request.md` (в директории `.agent/` целевого репозитория)
## Шаги
### 0.1. Проверить наличие .agent/metaagent-request.md
Если файл существует — распарсить, провалидировать и использовать.
Если нет — перейти к интервью (шаг 0.2).
### 0.2. Интервью с пользователем
Задать пользователю серию вопросов для сбора конфигурации.
**Сценарий интервью:**
```
MetaAgent: .agent/metaagent-request.md не найден. Давайте настроим сессию.
(или ответьте "default" — я выберу depth=4, light)
Q1: Это новый проект (greenfield) или работа с существующим кодом (existing)?
Варианты: new / existing / scaffold / default
Q2: Глубина проработки?
1-2: Scaffold — только структура, пустые модули
3-4: Light — быстрый дизайн + задачи, без расширений (рекомендуется default)
5-6: Standard — полный цикл с acceptance criteria
7-8: Deep — + ADR, risk register, alternative architecture
9-10: Maximum — + Red Team review, executable invariants
Варианты: число 1-10 / default
Q3 (если глубина >= 7): Нужны ADR (Architecture Decision Records)?
Варианты: yes / no / default
Q4 (если глубина >= 7): Нужен Risk Register?
Варианты: yes / no / default
Q5 (если глубина >= 9): Нужен Red Team Review?
Варианты: yes / no / default
```
**Правила обработки ответов:**
- Если пользователь ответил `default` или не ответил — применить значение по умолчанию для этого поля
- Если пользователь ответил `new``project_type = greenfield`
- Если `existing``project_type = existing`
### 0.3. Default config
```json
{
"depth": 4,
"design": {
"adr": false,
"alternative_arch": false
},
"red_team": false,
"risk_register": false,
"decomposition": {
"invariant_tests": false
},
"handoff": {
"layer_structure": false
}
}
```
Depth=4 (Light) означает:
- ANALYSIS — полный (определение типа проекта, извлечение требований)
- DESIGN — выполняется (если greenfield), но **без** ADR, Alternative Architecture, Risk Register
- DECOMPOSITION — задачи с acceptance criteria, **без** invariant-тестов
- SETUP — полный
- HANDOFF — плоский `.agent/` (без layer-структуры)
### 0.4. Запись .agent/metaagent-request.md
Если файла не было, создать его по результатам интервью с пометкой `Auto-generated`:
```markdown
# MetaAgent Request
# Auto-generated from user interview on {{ date }}
## Параметры сессии
| Функция | Вкл | Аргументы |
|---|---|---|
| ANALYSIS | ✓ | — |
| DESIGN | ✓ | adr={{ adr }}, alternative_arch={{ alt_arch }} |
| RED_TEAM | {{ red_team }} | — |
| RISK_REGISTER | {{ risk_register }} | — |
| DECOMPOSITION | ✓ | invariant_tests={{ invariant_tests }} |
| SETUP | ✓ | — |
| HANDOFF | ✓ | layer_structure={{ layer_structure }} |
## Глубина проработки
**Значение:** {{ depth }}
## Цель
{{ goal }}
```
### 0.5. Создание .agent/rules/
Создать директорию `.agent/rules/` в корне целевого проекта (если не существует).
Если `.agent/rules/project-rules.md` не существует — создать из шаблона `.agent/src/TEMPLATES/project-rules.md`:
```markdown
# Project Rules
Добавляйте сюда правила, которым агент обязан следовать во всех фазах.
```
### 0.6. Валидация config
Проверить совместимость параметров с depth:
```
depth < 3 → DESIGN пропускается (даже для greenfield)
depth < 7 → adr=false, alternative_arch=false, risk_register=false, invariant_tests=false
depth < 9 → red_team=false
```
Если depth несовместим с включёнными функциями — понизить функции до максимума, разрешённого depth.
## Выход
- `.agent/metaagent-request.md` (создан или подтверждён)
- config — словарь параметров для записи в checkpoints.json
## Критерии завершения
- [ ] `.agent/metaagent-request.md` существует (создан или найден)
- [ ] Config содержит depth, design.*, red_team, risk_register, decomposition.*, handoff.*
- [ ] Config совместим с depth (доп. функции отключены для малых depth)
- [ ] При отсутствии файла — проведено интервью, файл создан
+124
View File
@@ -0,0 +1,124 @@
# Протокол 00b: Миграция артефактов (MIGRATE)
## Цель
Обеспечить совместимость артефактов `.agent/` при изменении версии MetaAgent.
Позволяет обновлять проекты, созданные старой версией, без потери данных.
## Вход
- `VERSION` — текущая версия MetaAgent
- `.agent/checkpoints.json` — артефакты целевого проекта
- `.agent/` — остальные артефакты
## Шаги
### M1. Определить версию артефактов
Прочитать `.agent/checkpoints.json`:
```python
stored_version = checkpoints.get("metaagent_version", None)
current_version = read("VERSION").strip()
```
- Если `metaagent_version` отсутствует → артефакт создан **v0.x** (доверсионный)
- Если `metaagent_version` == `current_version` → пропустить миграцию
- Если `metaagent_version` < `current_version` → требуется миграция
### M2. Сравнение версий (SemVer)
Версии сравниваются по семантическому версионированию (`MAJOR.MINOR.PATCH`).
```python
def needs_migration(stored, current):
if stored is None:
return True
return parse_semver(stored) < parse_semver(current)
```
### M3. Матрица миграций
Каждая строка — набор шагов для перехода с одной версии на следующую.
| Из версии | В версию | Шаги миграции |
|---|---|---|
| v0.x (нет поля) | v1.0.0 | M3.1 — M3.4 |
| v1.0.0 | v1.1.0 | M3.5 — M3.6 (см. ниже) |
### M4. Шаги миграции v0.x → v1.0.0
... (шаги миграции остаются без изменений)
### M5. Шаги миграции v1.0.0 → v1.1.0
M3.5: Создать `.agent/rules/` с шаблоном `project-rules.md` (если не существует).
M3.6: Создать `.agent/archive/` (если не существует).
#### M3.1. Добавить metaagent_version
Записать в checkpoints.json:
```json
"metaagent_version": "1.1.0"
```
#### M3.2. Добавить config (default)
Если поля `config` нет в checkpoints.json — добавить config по умолчанию:
```json
"config": {
"depth": 4,
"design": { "adr": false, "alternative_arch": false },
"red_team": false,
"risk_register": false,
"decomposition": { "invariant_tests": false },
"handoff": { "layer_structure": false }
}
```
#### M3.3. Добавить фазу red_team
Если в `phases` нет ключа `red_team`:
```json
"red_team": "skipped"
```
#### M3.4. Создать layer-1/ (опционально, только если config.handoff.layer_structure)
Если включена layer_structure:
```bash
mkdir -p .agent/layer-1/adr
touch .agent/layer-1/adr/.gitkeep
```
Если `risk-register.md` уже существует на верхнем уровне — переместить в `.agent/layer-1/risk-register.md`.
### M4. После миграции — резюме
Записать в `.agent/migration-report.log`:
```
[MIGRATE] {{ timestamp }}
From: {{ from_version }}
To: {{ to_version }}
Steps applied: {{ step_list }}
Status: OK
```
## Выход
- Обновлённый `.agent/checkpoints.json` (metaagent_version + config)
- Опционально: `.agent/layer-1/` структура
- `.agent/migration-report.log`
## Критерии завершения
- [ ] metaagent_version в checkpoints.json == текущей версии из VERSION
- [ ] config присутствует в checkpoints.json
- [ ] phases.red_team присутствует (skipped, если не нужен)
- [ ] migration-report.log создан
- [ ] Все старые данные сохранены (ничего не удалено)
+139
View File
@@ -0,0 +1,139 @@
# Протокол 01: Анализ репозитория (ANALYSIS)
## Цель
Составить полную картину целевого репозитория: тип проекта, архитектура, стек, конвенции, состояние тестов, требования.
## Вход
- Целевой репозиторий (локальная копия)
- `.agent/metaagent-request.md` (конфигурация сессии: глубина, функции) — или auto-generated
- `.agent/checkpoints.json` (фаза analysis: pending)
## Шаги
### 0.0. Чтение конфигурации сессии
Прочитать config из checkpoints.json (установлен на фазе INIT через `00_CONFIG.md`).
Если config отсутствует или неполный — применить default:
```json
{
"depth": 4,
"design": { "adr": false, "alternative_arch": false },
"red_team": false,
"risk_register": false,
"decomposition": { "invariant_tests": false },
"handoff": { "layer_structure": false }
}
```
Записать (или подтвердить) конфигурацию в checkpoints.json:
```json
"config": {
"depth": 6,
"design": { "adr": true, "alternative_arch": true },
"red_team": false,
"risk_register": false,
"decomposition": { "invariant_tests": true },
"handoff": { "layer_structure": true }
}
```
Если `.agent/metaagent-request.md` не найден — использовать значения по умолчанию (depth=6, все базовые функции=true, расширенные=false).
### 1.0. Определение типа проекта
Просканировать корень репозитория и определить:
- **`existing`** — есть исходный код, тесты, система сборки (файлы `.py`, `.js`, `.ts`, `.rs`, `.go` и т.д. помимо конфигов и README)
- **`greenfield`** — репозиторий пуст или содержит только README, LICENSE, .gitignore
- **`scaffold`** — есть базовая структура (pyproject.toml/package.json), но нет значимого кода
Записать тип в analysis-report.md.
**Правило:** если проект `existing` — разделы 1.1–1.6 выполняются полностью. Если `greenfield` — разделы 1.2–1.5 заменяются на 1.7 (извлечение требований из README).
### 1.1. Общая информация
Прочитать и зафиксировать:
- **README** — описание проекта, how to build/test/run
- **Лицензия** — какой LICENSE
- **CI/CD** — `.github/workflows/`, `.gitlab-ci.yml`, `Jenkinsfile`, `Makefile` и т.д.
- **Главные точки входа** — `main.py`, `index.js`, `cmd/` и т.д.
- **Система сборки** — `package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `CMakeLists.txt`
### 1.2. Стек технологий (только для existing/scaffold)
Определить:
- **Язык(и)** — Python, TypeScript, Go, Rust и т.д.
- **Фреймворк** — FastAPI, Next.js, React, Actix и т.д.
- **База данных** — PostgreSQL, SQLite, MongoDB и т.д.
- **Тестовый раннер** — pytest, jest, vitest, go test
- **Пакетный менеджер** — pip/poetry, npm/yarn/pnpm, cargo, go modules
- **Линтер/форматтер** — ruff, eslint, prettier, rustfmt, gofmt
### 1.3. Архитектура (только для existing/scaffold)
- **Структура директорий** — записать схему (можно `tree /F`, но не более 3 уровней глубины)
- **Архитектурный паттерн** — MVC, Clean Architecture, модульный монолит, микросервисы
- **Ключевые модули/пакеты** — перечислить с кратким описанием
- **Внешние зависимости** — основные библиотеки
### 1.4. Конвенции кода (только для existing/scaffold)
- **Стиль кода** — судя по линтеру и примерам: именование, импорты, типизация
- **Паттерны** — как организованы роуты, хендлеры, модели, тесты
- **Обработка ошибок** — как принято обрабатывать ошибки в проекте
- **Логирование** — используется ли логгер, какой уровень
### 1.5. Тесты (только для existing/scaffold)
- **Какие тесты есть** — unit, integration, e2e
- **Где лежат** — `tests/`, `__tests__/`, рядом с модулями
- **Запуск** — команда для запуска всех тестов
- **Текущее состояние** — запустить тесты, записать результат (сколько всего, сколько пройдено/упало)
- **Покрытие** — есть ли метрики покрытия
### 1.6. Базовая проверка (только для existing/scaffold)
- **Собирается ли проект?** — запустить сборку
- **Запускается ли проект?** — если возможно, проверить старт
- **Чистый ли git status?** — нет ли незакоммиченных изменений
### 1.7. Извлечение требований (только для greenfield/scaffold)
Если README содержит описание будущего проекта — извлечь и структурировать:
**Функциональные требования:**
- Пользовательские истории (user stories)
- Основные сценарии использования
- Входные/выходные данные системы
**Нефункциональные требования:**
- Технологические предпочтения (язык, фреймворк, БД)
- Требования к производительности, безопасности
- Ограничения (сроки, платформа, окружение)
**Бизнес-контекст:**
- Цель системы (зачем)
- Целевая аудитория
- Ключевые метрики успеха
**Сомнительные/неясные требования:**
- Вопросы, которые нужно задать пользователю перед проектированием
- Противоречия в README
## Выход
`.agent/analysis-report.md` по шаблону `TEMPLATES/analysis-report.md`.
Обновить checkpoints.json: `phases.analysis = "completed"`. Если проект `greenfield`, также установить `project_type = "greenfield"`.
## Критерии завершения фазы
- [ ] Тип проекта определён (existing / greenfield / scaffold)
- [ ] Все соответствующие разделы (1.1–1.7) выполнены
- [ ] `.agent/analysis-report.md` создан и заполнен
- [ ] checkpoints.json обновлён
+173
View File
@@ -0,0 +1,173 @@
# Протокол 02: Архитектурное проектирование (DESIGN)
## Цель
Спроектировать архитектуру, модули, данные и интерфейсы для greenfield/scaffold-проекта на основе требований из analysis-report.
## Вход
- `.agent/analysis-report.md` (project_type: greenfield или scaffold)
- `.agent/metaagent-request.md` (конфигурация сессии: adr, alternative_arch, risk_register)
- `.agent/checkpoints.json` (фаза design: pending)
## Правила
1. **Реалистичность** — архитектура должна быть реализуема исполнительным агентом за 1 сессию (до 10 задач)
2. **Документируемость** — каждый модуль, модель и интерфейс описывается в design-report.md
3. **Тестируемость** — каждый компонент проектируется с учётом того, как его тестировать
4. **Итеративность** — первая версия должна быть минимально рабочей (MVP), расширения — отдельными задачами
## Шаги
### 2.1. Технологический стек
Если стек не указан в README — предложить обоснованный выбор. Если указан — зафиксировать.
Для каждого компонента указать:
- Язык и версия
- Фреймворк / библиотека
- База данных (движок, схема)
- Инфраструктура (Docker, CI/CD, хостинг)
### 2.2. High-level архитектура
Описать общую структуру системы:
- **Архитектурный паттерн** — монолит, модульный монолит, микросервисы, слоистая, луковая и т.д.
- **Компоненты и их ответственность** — что делает каждый модуль/сервис
- **Схема взаимодействия** — текстовое описание потоков данных
Формат (text diagram):
```
[Client] → HTTP → [API Gateway] → [Auth Service]
[Core Service] → [Database]
[External API] → [3rd Party]
```
### 2.3. Модули проекта
Разбить систему на модули/пакеты. Для каждого:
| Поле | Описание |
|---|---|
| **Имя модуля** | `app/services/cashflow.py` |
| **Ответственность** | Что делает |
| **Ключевые классы/функции** | Только сигнатуры (без реализации) |
| **Зависимости** | Какие модули нужны этому |
| **Контракт** | Что экспортирует/предоставляет |
### 2.4. Модели данных
Описать основные сущности, их поля и связи:
```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"}
]
}
```
Если используется ORM — указать аннотации/декораторы.
Если БД — схему таблиц, индексы, ключи.
### 2.5. API интерфейсы
Если проектируется API — описать эндпоинты:
| Метод | Путь | Описание | Request | Response | Статусы |
|---|---|---|---|---|---|
| GET | /transactions | Список транзакций | ?page, ?limit | [Transaction] | 200 |
| POST | /transactions | Создать транзакцию | CreateTransactionDTO | Transaction | 201, 400 |
Если GUI — описать ключевые страницы/экраны.
Если CLI — описать команды.
### 2.6. Обработка ошибок
- Стратегия ошибок: исключения, Result-тип, коды ошибок
- Формат ошибок в API: `{ "error": "...", "code": "...", "details": {} }`
- Логирование: какой уровень для каких событий
### 2.7. Стратегия тестирования
- Какие тесты нужны (unit, integration, e2e)
- Как изолировать зависимости (mocks, fakes, testcontainers)
- Команда запуска тестов
### 2.8. Alternative Architecture (если config.alternative_arch = yes)
Описать **минимум одну принципиально иную архитектуру** и причину отказа:
| Критерий | Выбранная архитектура | Альтернатива |
|---|---|---|
| Название | Модульный монолит | Микросервисы / Событийная / и т.д. |
| Сложность реализации | Низкая | Высокая (3+ сервиса) |
| Масштабирование | Вертикальное | Горизонтальное |
| Почему не выбрана | — | Избыточно для MVP |
Это снижает риск архитектурной инерции: решение становится осознанным, а не единственным возможным.
### 2.9. ADR (если config.adr = yes)
Для каждого ключевого архитектурного решения (стек, БД, паттерн, структура модулей) создать отдельный ADR-файл:
```
.agent/layer-1/adr/001-технологический-стек.md
.agent/layer-1/adr/002-модульный-монолит.md
.agent/layer-1/adr/003-json-хранение.md
```
Формат — по шаблону `TEMPLATES/adr-NNNN.md`.
### 2.10. Risk Register (если config.risk_register = yes)
Создать `.agent/layer-1/risk-register.md` по шаблону `TEMPLATES/risk-register.md`:
| # | Assumption | Impact if wrong | Mitigation | Review trigger |
|---|---|---|---|---|
Задокументировать **неявные допущения**, на которых держится архитектура. Это даёт future-агентам знать, что можно пересматривать в первую очередь.
### 2.11. Группировка в задачи
На основе спроектированных модулей и моделей предварительно наметить группировку в задачи (по модулям). Это будет входом для DECOMPOSITION.
```
T1: Инициализация проекта + зависимости
T2: Модель данных (сущности, миграции)
T3: Cashflow Service (core logic)
T4: API endpoints
...и т.д.
```
## Выход
- `.agent/design-report.md` по шаблону `TEMPLATES/design-report.md`
- `.agent/layer-1/adr/*.md` (если adr=yes)
- `.agent/layer-1/risk-register.md` (если risk_register=yes)
- Предварительная группировка задач (для передачи в DECOMPOSITION)
Обновить checkpoints.json: `phases.design = "completed"`.
## Критерии завершения фазы
- [ ] Технологический стек определён
- [ ] High-level архитектура описана
- [ ] Модули и их ответственность описаны
- [ ] Модели данных спроектированы
- [ ] API/интерфейсы описаны (если применимо)
- [ ] Стратегия тестирования определена
- [ ] Alternative Architecture описана (если config требует)
- [ ] ADR созданы (если config требует)
- [ ] Risk Register создан (если config требует)
- [ ] Задачи предварительно сгруппированы
- [ ] `.agent/design-report.md` создан
- [ ] checkpoints.json обновлён
+80
View File
@@ -0,0 +1,80 @@
# Протокол 02b: Red Team Review (опционально)
## Цель
Преднамеренно попытаться разрушить спроектированную архитектуру, чтобы найти скрытые проблемы до начала реализации.
## Вход
- `.agent/design-report.md`
- `.agent/layer-1/adr/*.md` (если созданы)
- `.agent/metaagent-request.md` (глубина проработки >= 9)
## Когда выполняется
Только если `config.red_team = yes` (глубина 9-10). Выполняется **после** DESIGN, **до** DECOMPOSITION.
## Шаги
### RT1. Поиск скрытых зависимостей
Проверить каждый модуль на наличие неявных связей:
- Есть ли циклические зависимости между модулями?
- Есть ли модуль, который знает слишком много о других?
- Есть ли скрытый vendor lock-in (БД, облачный провайдер, внешний API)?
### RT2. Точки отказа
Для каждого внешнего интерфейса (API, БД, файловая система):
- Что произойдёт при отказе компонента?
- Есть ли fallback?
- Что произойдёт при невалидных входных данных?
### RT3. Масштабирование
Оценить поведение системы при:
- 10x рост данных
- 100x рост данных
- Добавлении нового пользователя / клиента
### RT4. Security (если применимо)
- Какие данные передаются по сети?
- Есть ли аутентификация?
- Хранятся ли секреты в коде?
### RT5. Consistency
Проверить design-report и ADR на противоречия:
- Одна сущность описана по-разному в двух местах?
- API-контракт не соответствует модели данных?
- Технологический стек противоречит нефункциональным требованиям?
## Выход
`.agent/layer-1/red-team-report.md` с секциями:
```
## Найденные проблемы
| # | Проблема | Серьёзность | Рекомендация |
|---|---|---|---|
## Отклонённые атаки (что пытались сломать — но не сломалось)
| # | Гипотеза | Почему не подтвердилась |
|---|---|---|
```
Обновить risk-register.md (если существует) новыми рисками.
## Критерии завершения
- [ ] Все 5 секций (RT1-RT5) проверены
- [ ] Найденные проблемы записаны в red-team-report.md
- [ ] Если найдены критические проблемы — design-report должен быть исправлен
- [ ] Risk Register дополнен (если существует)
+133
View File
@@ -0,0 +1,133 @@
# Протокол 03: Декомпозиция задач (DECOMPOSITION)
## Цель
Разбить цель пользователя (и архитектурный план, если есть) на атомарные, независимо выполнимые задачи и записать их в манифест.
## Вход
- `.agent/analysis-report.md`
- `.agent/design-report.md` (опционально — для greenfield/scaffold)
- `.agent/layer-1/adr/*.md` (опционально)
- `.agent/layer-1/risk-register.md` (опционально)
- `.agent/metaagent-request.md` (конфигурация сессии)
- Цель пользователя (из checkpoints.json)
- `.agent/checkpoints.json` (фаза decomposition: pending)
## Правила декомпозиции
### 3.1. Принципы
1. **Атомарность** — одна задача = одна логическая единица работы, которую можно выполнить и проверить за один подход
2. **Независимость (макс.)** — минимизировать зависимости между задачами
3. **Тестируемость** — каждая задача имеет измеримые acceptance criteria
4. **Границы** — задача не должна выходить за пределы, указанные в `BOUNDARIES.md`
5. **Порядок** — задачи с зависимостями выполняются строго последовательно
### 3.2. Размер задачи
Задача должна укладываться в **1-2 часа работы исполнительного агента**. Если задача крупнее — разбить на подзадачи.
Признак слишком крупной задачи:
- Нельзя сформулировать acceptance criteria одной строкой
- Затрагивает 5+ файлов
- Содержит союзы "и", "а также", "после чего"
### 3.3. Структура задачи
Каждая задача содержит:
| Поле | Описание | Пример |
|---|---|---|
| `id` | Уникальный идентификатор | `T1`, `T2` |
| `title` | Заголовок (что сделать) | "Добавить модель User" |
| `description` | Описание (как и зачем) | "Создать SQLAlchemy модель..." |
| `type` | Тип задачи | `feature`, `refactor`, `test`, `fix`, `config`, `design`, `docs` |
| `status` | Статус задачи | `pending`, `in_progress`, `completed`, `failed`, `archived` |
| `files` | Список файлов, которые нужно создать/изменить | `["app/models/user.py"]` |
| `depends_on` | ID задач, от которых зависит | `[]` или `["T0"]` |
| `acceptance_criteria` | Список критериев приёмки (3-5 пунктов) | `["Модель проходит миграцию"]` |
| `context` | Доп. информация (ссылки на доки, примеры, релевантные секции из design-report) | `"Смотри app/models/base.py"` |
### 3.4. Типы задач
| Тип | Описание |
|---|---|
| `config` | Настройка окружения, зависимостей, CI, инициализация проекта |
| `design` | Архитектурное/дизайнерское решение без кода |
| `feature` | Новая функциональность |
| `refactor` | Изменение структуры без изменения поведения |
| `test` | Добавление/исправление тестов |
| `fix` | Исправление бага |
| `docs` | Документация |
| `invariant` | Тест, проверяющий архитектурный инвариант (см. 3.7) |
### 3.5. Зелёная декомпозиция (для greenfield/scaffold)
Если есть `.agent/design-report.md` — задачи формируются на основе группировки из дизайна:
1. **T1: init** — инициализация проекта, зависимости, конфиги, scaffold
2. **T2..Tn: features** — модули/функциональность по одному
3. **Tn+1: tests** — тесты на каждый модуль (можно в составе feature-задачи)
4. **Tn+2: polish** — документация, форматирование, финальная проверка
### 3.6. Сортировка
Задачи в манифесте располагаются в порядке выполнения:
1. Сначала задачи без зависимостей
2. Потом те, чьи зависимости уже выполнены
3. Последними — задачи с наибольшим числом зависимостей
### 3.7. Executable Invariants (если config.invariant_tests = yes)
Для каждого ADR (из layer-1/adr/) создать задачу типа `invariant` — тест, проверяющий архитектурное правило.
**Правила превращения ADR в инварианты:**
| ADR | Инвариант-тест |
|---|---|
| "Модуль X не зависит от Y" | `test_x_does_not_import_y.py` — import test |
| "Слой Model не знает о CLI" | `test_model_layer_imports.py` — проверка import graph |
| "Все исключения кастомные" | `test_custom_exceptions.py` — проверка hierarchy |
| "Интерфейс репозитория не泄漏 implementation details" | `test_repository_interface.py` — ABC check |
**Формат задачи-инварианта:**
```json
{
"id": "I1",
"title": "Инвариант: model не импортирует cli",
"type": "invariant",
"files": ["tests/invariants/test_layer_imports.py"],
"depends_on": ["T2"],
"acceptance_criteria": [
"Тест проверяет, что cashflow_model не импортирует cli, sync, engine",
"Тест проходит на пустом проекте (до реализации функциональности)"
]
}
```
Инварианты размещаются в `tests/invariants/` и запускаются вместе с основными тестами.
## Выход
- `.agent/task-manifest.json` — по схеме `TEMPLATES/task-manifest.json`
- `.agent/task-manifest.md` — по шаблону `TEMPLATES/task-manifest.md`
Обновить checkpoints.json:
- `phases.decomposition = "completed"`
- `tasks` = полный массив задач со статусом `pending`
> **Примечание:** после HANDOFF завершённые задачи будут архивированы —
> полное описание уходит в `.agent/archive/tasks/`, в манифесте остаётся
> one-liner с `"status": "archived"`.
## Критерии завершения фазы
- [ ] Цель разбита на атомарные задачи
- [ ] Для каждой задачи указаны acceptance criteria
- [ ] Для каждой задачи указаны affected files
- [ ] Зависимости между задачами корректны (нет циклов)
- [ ] Invariant-задачи созданы для каждого ADR (если config требует)
- [ ] `.agent/task-manifest.json` и `.agent/task-manifest.md` созданы
- [ ] checkpoints.json обновлён
@@ -0,0 +1,126 @@
# Протокол 04: Настройка окружения (SETUP)
## Цель
Обеспечить рабочее окружение, в котором исполнительный агент может сразу выполнять задачи.
## Вход
- `.agent/analysis-report.md`
- `.agent/design-report.md` (опционально, для greenfield)
- `.agent/task-manifest.json`
- `.agent/checkpoints.json` (фаза environment: pending)
## Поведение в зависимости от типа проекта
Фаза SETUP работает по-разному для `existing` и `greenfield/scaffold` проектов.
---
## Ветка A: existing/scaffold проект
### 4A.1. Зависимости
- Установить все зависимости согласно документации проекта
- Если есть `requirements.txt`, `pyproject.toml`, `package.json`, `Cargo.toml` и т.д. — выполнить установку
- Если в проекте используется виртуальное окружение (venv, .venv, conda) — активировать или создать
- Если в проекте используется Docker — проверить, что образ собирается
**Правило:** если установка зависимостей требует нестандартных шагов, описанных в README — строго следовать им. Если шаги не описаны — запросить у пользователя.
### 4A.2. Конфигурация
- Проверить наличие конфигурационных файлов (`.env.example`, `.env`, `config.yaml`)
- Если есть `.env.example`, скопировать в `.env` с настройками по умолчанию
- Если проекту требуется БД — проверить строку подключения, при необходимости создать БД или использовать SQLite для разработки
- Настроить pre-commit хуки, если они есть в проекте
### 4A.3. Линтеры и форматтеры
- Запустить линтер на всём проекте: записать результат
- Если линтер выдаёт ошибки — не исправлять, только зафиксировать в отчёте
- Убедиться, что исполнительный агент может запускать линтер (записать команду)
### 4A.4. Baseline-тесты
- Запустить все тесты проекта
- Записать в `.agent/baseline-test-report.log`:
- Команда запуска
- Общее количество тестов
- Пройдено / упало / пропущено
- Время выполнения
- Список упавших тестов (если есть)
- Если тесты не проходят — указать это в отчёте, но **не исправлять**
### 4A.5. Сборка проекта
- Выполнить полную сборку/компиляцию проекта
- Записать результат (успех/ошибка с логом)
- Сборка должна проходить без ошибок. Если не собирается — остановиться, сообщить пользователю.
---
## Ветка B: greenfield проект
### 4B.1. Инициализация проекта
- Создать базовую структуру директорий согласно design-report.md
- Инициализировать пакетный менеджер:
- Python: `pyproject.toml` (poetry, pdm, hatch) или `requirements.txt`
- Node: `package.json` и `npm init` / `yarn init`
- Go: `go mod init`
- Rust: `cargo init`
- Настроить базовый конфиг: `.env.example`, `config/` и т.д.
- Настроить линтер/форматтер: `ruff`, `eslint`, `gofmt` и т.д.
### 4B.2. Scaffold-код
Создать пустые заглушки для модулей, описанных в design-report:
```python
# app/services/cashflow.py — заглушка
class CashflowService:
"""TBD — реализация в задаче T3"""
pass
```
Назначение: фиксировать структуру, чтобы исполнительный агент не думал о ней, а сразу писал реализацию.
### 4B.3. Установка зависимостей
- Установить базовые зависимости согласно стеку из design-report
- Если проект использует БД — установить драйвер/ORM
- Если проект использует API — установить фреймворк (FastAPI, Express и т.д.)
- Установить dev-зависимости: линтер, тестовый раннер, type stubs
### 4B.4. Базовые тесты (scaffold)
- Создать пустой тестовый файл для каждого модуля
- Настроить тестовый раннер (pytest, jest и т.д.)
- Записать в `.agent/baseline-test-report.log`: "0 tests — greenfield, scaffold готов"
### 4B.5. Проверка сборки
- Убедиться, что проект импортируется без ошибок
- Убедиться, что линтер проходит (без кода он должен проходить)
- Убедиться, что тестовый раннер запускается (0 tests, exit code 0)
---
## Выход
- Работоспособное окружение / инициализированный проект
- `.agent/baseline-test-report.log` — результат прогона тестов
- `.agent/setup-report.log` — лог установки зависимостей и сборки
Обновить checkpoints.json: `phases.environment = "completed"`.
## Критерии завершения фазы
- [ ] Зависимости установлены / проект инициализирован
- [ ] Проект собирается / импортируется без ошибок
- [ ] Baseline-тесты запущены, результат записан
- [ ] `.agent/baseline-test-report.log` и `.agent/setup-report.log` созданы
- [ ] checkpoints.json обновлён
Если проект не собирается — **фаза считается проваленной**, checkpoints.json отмечает `phases.environment = "failed"`, управление возвращается пользователю.
+188
View File
@@ -0,0 +1,188 @@
# Протокол 05: Передача исполнительному агенту (HANDOFF)
## Цель
Подготовить и передать исполнительному агенту полный контекст для работы: задачи, окружение, правила.
## Вход
- `.agent/analysis-report.md`
- `.agent/design-report.md` (опционально, для greenfield)
- `.agent/layer-1/adr/*.md` (опционально)
- `.agent/layer-1/risk-register.md` (опционально)
- `.agent/layer-1/red-team-report.md` (опционально)
- `.agent/task-manifest.json`
- `.agent/task-manifest.md`
- `.agent/baseline-test-report.log`
- `.agent/checkpoints.json` (все предыдущие фазы: completed)
## Шаги
### 5.1. Архивация завершённых артефактов
Перед валидацией и передачей выполнить архивирование.
**Архивировать завершённые задачи:**
Для каждой задачи в `task-manifest.json` со статусом `completed`:
1. Создать `.agent/archive/tasks/<id>.json` — перенести полное описание задачи (все поля)
2. В `task-manifest.json` заменить задачу на one-liner:
```json
{ "id": "<id>", "title": "<title>", "status": "archived" }
```
**Архивировать чекпоинты:**
Если `checkpoints.json` уже существует — сохранить предыдущую версию в `.agent/archive/checkpoints/<last_updated>.json`.
**Создать индекс архива:**
```json
{
"version": "1.1.0",
"archived_at": "<timestamp>",
"tasks": [
{ "id": "T1", "title": "...", "archived_at": "<timestamp>" }
],
"checkpoints": [
{ "file": "checkpoints/2026-07-15T10-00-00.json", "archived_at": "<timestamp>" }
]
}
```
### 5.2. Валидация
Перед передачей проверить:
- [ ] Все фазы отмечены как `completed` в checkpoints.json
- [ ] `.agent/` содержит все обязательные файлы:
- `checkpoints.json`
- `analysis-report.md`
- `task-manifest.json` + `task-manifest.md`
- `baseline-test-report.log`
- `setup-report.log`
- `src/META_AGENT_GUIDE.md`
- `src/BOUNDARIES.md`
- `src/VERSION`
- `src/PROTOCOLS/`
- `src/TEMPLATES/`
- `rules/project-rules.md`
- `archive/index.json`
- [ ] Для greenfield: `design-report.md` присутствует
- [ ] В task-manifest.json нет циклических зависимостей
- [ ] Все acceptance criteria сформулированы измеримо
- [ ] Для каждой задачи указаны affected files
- [ ] В репозитории нет незакоммиченных изменений (кроме `.agent/`)
- [ ] `.agent/src/` содержит актуальные исходники MetaAgent (META_AGENT_GUIDE.md, PROTOCOLS/, TEMPLATES/, BOUNDARIES.md, VERSION)
- [ ] `AGENTS.md` присутствует в корне репозитория
- [ ] `.agent/rules/` содержит `project-rules.md`
**Дополнительные проверки (если config включает):**
- [ ] ADR присутствуют (если adr=yes)
- [ ] Risk Register заполнен (если risk_register=yes)
- [ ] Red Team Report есть (если red_team=yes)
- [ ] Invariant-задачи в манифесте (если invariant_tests=yes)
### 5.3. Layer-структура .agent/
Если `config.layer_structure = yes`, организовать артефакты по слоям:
```
.agent/
layer-0/
checkpoints.json # всегда (ядро)
session-summary.md # краткая сводка сессии (создаётся здесь)
layer-1/
adr/ # ADR (опционально)
risk-register.md # (опционально)
red-team-report.md # (опционально)
layer-2/
analysis-report.md
design-report.md
design-report.md
layer-3/
handoff-summary.md
task-manifest.json
task-manifest.md
baseline-test-report.log
setup-report.log
```
Если `layer_structure = no` — артефакты остаются плоскими в `.agent/`, как раньше.
### 5.4. Создать handoff-summary.md
Заполнить по шаблону `TEMPLATES/handoff-summary.md`:
- **Session Info** — ID, цель, дата
- **Configuration** — какие функции были включены, глубина
- **Repo Summary** — краткая выжимка из analysis-report
- **Environment Status** — результат сборки и тестов
- **Design Summary** (если есть design-report) — ключевые архитектурные решения
- **ADR Summary** (если adr=yes) — какие решения задокументированы
- **Risk Register** (если risk_register=yes) — основные допущения
- **Task Overview** — количество задач, типы, список
- **Next Steps** — с какой задачи начинать исполнительному агенту
- **Project Rules** — ссылка на `.agent/rules/project-rules.md` (передаётся exec-агенту)
- **Archive** — ссылка на `.agent/archive/index.json` (история завершённых задач)
- **Caveats** — известные проблемы, ограничения, неясные моменты
- **Checkpoints** — актуальное состояние чекпоинтов
### 5.5. Финализировать checkpoints
- Отметить `phases.handoff = "completed"`
- Записать финальный `last_updated`
### 5.6. Сигнал
Сообщить пользователю/оркестратору:
```
HANDOFF COMPLETE
Session: <session_id>
Target: <target_repo>
Type: <existing | greenfield | scaffold>
Config: depth=<N>, adr=<yes|no>, red_team=<yes|no>, ...
Tasks: <count> tasks ready
Исполнительный агент может начинать с задачи <T1>.
Контекст: .agent/handoff-summary.md
Манифест: .agent/task-manifest.json
```
## Что получает исполнительный агент
1. **Целевой репозиторий** — полностью настроенный, с установленными зависимостями
2. **`.agent/`** — директория со всеми артефактами (layer-структура или плоская)
3. **`task-manifest.json`** — машиночитаемый список задач
4. **`task-manifest.md`** — человекочитаемый список задач
5. **`handoff-summary.md`** — итоговая сводка
6. **`checkpoints.json`** — актуальное состояние (исполнительный агент будет его обновлять)
7. **`layer-1/adr/*.md`** (опционально) — ключевые решения
8. **`layer-1/risk-register.md`** (опционально) — допущения
9. **`layer-2/analysis-report.md`** — полный анализ репозитория (справочно)
10. **`layer-2/design-report.md`** (только для greenfield) — архитектурный план
11. **`layer-3/baseline-test-report.log`** — baseline тестов (чтобы не сломать существующее)
12. **`.agent/src/`** — полные исходники MetaAgent (справочно, всегда присутствуют)
13. **`.agent/rules/`** — пользовательские правила проекта
14. **`AGENTS.md`** — инструкция для AI-агента в корне проекта (всегда присутствует)
15. **`.agent/archive/`** — архив завершённых задач, чекпоинтов и устаревших артефактов
## Выход
- `.agent/layer-0/session-summary.md` (если layer_structure=yes)
- `.agent/layer-3/handoff-summary.md`
- `.agent/layer-0/checkpoints.json` (финальный)
- `.agent/archive/index.json` (создаётся при архивации)
## Критерии завершения
- [ ] Все артефакты на месте (с учётом layer-структуры)
- [ ] `.agent/src/` содержит актуальные исходники MetaAgent
- [ ] `.agent/rules/` содержит `project-rules.md`
- [ ] `AGENTS.md` присутствует в корне репозитория
- [ ] `.agent/archive/index.json` создан, завершённые задачи архивированы
- [ ] handoff-summary.md заполнен (включая config, design summary, ADR summary, archive)
- [ ] 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.
+101
View File
@@ -0,0 +1,101 @@
# 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 }}
{% if project_type == "existing" or project_type == "scaffold" %}
## 2. Стек технологий
| Компонент | Значение |
|---|---|
| Язык | {{ language }} |
| Фреймворк | {{ framework }} |
| База данных | {{ database }} |
| Тестовый раннер | {{ test_runner }} |
| Пакетный менеджер | {{ package_manager }} |
| Линтер/форматтер | {{ linter }} |
## 3. Архитектура
```
{{ directory_tree }}
```
**Паттерн:** {{ architecture_pattern }}
**Ключевые модули:**
| Модуль | Описание |
|---|---|
| {{ module }} | {{ description }} |
## 4. Конвенции
- **Стиль:** {{ code_style }}
- **Импорты:** {{ import_style }}
- **Типизация:** {{ typing_usage }}
- **Обработка ошибок:** {{ error_handling }}
- **Логирование:** {{ logging }}
## 5. Тесты
- **Команда запуска:** `{{ test_command }}`
- **Всего тестов:** {{ total_tests }}
- **Пройдено:** {{ passed }}
- **Упало:** {{ failed }}
- **Пропущено:** {{ skipped }}
- **Упавшие тесты:**
{% for test in failed_tests %}
- `{{ test }}`
{% endfor %}
## 6. Базовая проверка
- **Сборка:** {{ build_status }}
- **Запуск:** {{ run_status }}
- **Git status:** {{ git_status }}
{% endif %}
{% if project_type == "greenfield" or project_type == "scaffold" %}
## 7. Требования (из README)
### Функциональные требования
{% for req in functional_requirements %}
- {{ req }}
{% endfor %}
### Нефункциональные требования
{% for req in non_functional_requirements %}
- {{ req }}
{% endfor %}
### Бизнес-контекст
{% for item in business_context %}
- {{ item }}
{% endfor %}
### Неясные моменты / Вопросы
{% for question in open_questions %}
- {{ question }}
{% endfor %}
{% endif %}
## 8. Примечания
{{ notes }}
+120
View File
@@ -0,0 +1,120 @@
# 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. Модели данных
### Сущности
{% for entity in entities %}
### {{ entity.name }}
| Поле | Тип | Ограничения | Описание |
|---|---|---|---|
{% for field in entity.fields %}
| {{ field.name }} | {{ field.type }} | {{ field.constraints }} | {{ field.description }} |
{% endfor %}
**Связи:** {{ entity.relationships }}
{% endfor %}
## 5. API / Интерфейсы
{% if has_api %}
| Метод | Путь | Описание | Request | Response |
|---|---|---|---|---|
{% for endpoint in api_endpoints %}
| {{ endpoint.method }} | {{ endpoint.path }} | {{ endpoint.description }} | {{ endpoint.request }} | {{ endpoint.response }} |
{% endfor %}
{% endif %}
{% if has_gui %}
**Экраны:** {{ gui_screens }}
{% endif %}
{% if has_cli %}
**Команды:** {{ cli_commands }}
{% endif %}
## 6. Обработка ошибок
- **Стратегия:** {{ error_strategy }}
- **Формат ошибок:** {{ error_format }}
- **Логирование:** {{ logging_strategy }}
## 7. Тестирование
- **Unit-тесты:** {{ unit_test_strategy }}
- **Integration-тесты:** {{ integration_test_strategy }}
- **Mock-стратегия:** {{ mock_strategy }}
- **Команда запуска:** `{{ test_command }}`
## 8. Alternative Architecture (если применимо)
| Критерий | Выбранная архитектура | Альтернатива |
|---|---|---|
| Название | {{ chosen_arch }} | {{ alt_arch }} |
| Сложность | {{ chosen_complexity }} | {{ alt_complexity }} |
| Почему не выбрана | — | {{ alt_rejection_reason }} |
## 9. ADR Reference (если применимо)
| ID | Решение | Файл |
|---|---|---|
{% for adr in adr_list %}
| {{ adr.id }} | {{ adr.title }} | `{{ adr.path }}` |
{% endfor %}
## 10. Risk Register (если применимо)
| # | Assumption | Impact | Mitigation |
|---|---|---|---|
{% for risk in risk_list %}
| {{ risk.id }} | {{ risk.assumption }} | {{ risk.impact }} | {{ risk.mitigation }} |
{% endfor %}
## 11. Предварительная группировка задач
| Задача | Описание | Тип |
|---|---|---|
| T1 | {{ task_1 }} | config |
| T2 | {{ task_2 }} | feature |
| T3 | {{ task_3 }} | feature |
| T4 | {{ task_4 }} | test |
## 12. Примечания
{{ notes }}
+84
View File
@@ -0,0 +1,84 @@
# Handoff Summary
## Session Info
- **Session ID:** `{{ session_id }}`
- **Target Repo:** `{{ target_repo }}`
- **Goal:** {{ goal }}
- **Date:** {{ date }}
- **Duration:** {{ duration }}
- **Depth:** {{ depth }}
- **Config:** {{ config_summary }}
## Repo Summary
{{ repo_summary }}
## Project Type
- **Type:** {{ project_type }}
- **Design report:** {% if project_type == "greenfield" or project_type == "scaffold" %}`.agent/design-report.md`{% else %}—{% endif %}
## ADR Summary (если применимо)
{% if adr_count > 0 %}
Создано ADR: {{ adr_count }}
{% for adr in adr_list %}
- `{{ adr.path }}` — {{ adr.title }}
{% endfor %}
{% endif %}
## Risk Register (если применимо)
{% if risk_count > 0 %}
Задокументировано допущений: {{ risk_count }}
Наиболее критичное: {{ top_risk }}
{% endif %}
## Environment Status
- **Build:** {{ build_status }}
- **Tests:** {{ tests_passed }}/{{ tests_total }} passed
- **Baseline log:** `.agent/baseline-test-report.log`
- **Dependencies:** {{ deps_status }}
## Task Overview
| Status | Count |
|---|---|
| Total | {{ total }} |
| Pending | {{ pending }} |
| In Progress | {{ in_progress }} |
| Completed | {{ completed }} |
| Failed/Skipped | {{ failed }} |
**Task by type:**
{% for type, count in tasks_by_type %}
- {{ type }}: {{ count }}
{% endfor %}
## Tasks (ordered)
{% for task in tasks %}
### {{ task.id }}: {{ task.title }}
- Type: {{ task.type }}
- Depends on: {{ task.depends_on | default("—") }}
- Files: {{ task.files | join(", ") }}
- Status: {{ task.status }}
{% endfor %}
## Next Steps
Исполнительный агент начинает с задачи **{{ first_task }}**.
## Caveats
{% for caveat in caveats %}
- {{ caveat }}
{% endfor %}
## Checkpoints
Файл: `.agent/checkpoints.json`
Актуальное состояние чекпоинтов прилагается.
+38
View File
@@ -0,0 +1,38 @@
# MetaAgent Request
# Для ручного заполнения перед запуском MetaAgent.
# Поместите этот файл в .agent/metaagent-request.md целевого репозитория.
# Если файл отсутствует — MetaAgent проведёт интервью (PROTOCOLS/00_CONFIG.md).
# Ответьте "default" на любой вопрос — будет использовано значение по умолчанию.
## Параметры сессии
| Функция | Вкл | Аргументы |
|---|---|---|
| ANALYSIS | ✓ | — |
| DESIGN | ✓ | adr=yes, alternative_arch=yes |
| RED_TEAM | ✗ | — |
| RISK_REGISTER | ✗ | — |
| DECOMPOSITION | ✓ | invariant_tests=yes |
| SETUP | ✓ | — |
| HANDOFF | ✓ | layer_structure=yes |
## Глубина проработки
**Значение:** 6 (1-10)
| Уровень | Название | Описание |
|---|---|---|
| 1-2 | Scaffold | Только структура проекта + пустые модули |
| 3-4 | Light | (default) Быстрый дизайн + задачи без расширений |
| 5-6 | Standard | Полный ANALYSIS→DESIGN→DECOMP→SETUP→HANDOFF |
| 7-8 | Deep | Standard + ADR, Risk Register, Alternative Architecture |
| 9-10 | Maximum | Deep + Red Team Review, Executable Invariants |
## Цель
Сформулируйте задачу для MetaAgent.
## Дополнительно
- **Boundaries:** (опционально) ограничения, которые нельзя нарушать
- **Target:** путь к репозиторию или URL
+17
View File
@@ -0,0 +1,17 @@
# Project Rules
Правила, которым агент обязан следовать во всех фазах.
Добавляйте сюда условия, которые должны соблюдаться всегда — они будут прочитаны
перед началом каждой фазы и учтены при декомпозиции и реализации.
## Обязательные правила
- (укажите правила, например: «Всегда использовать tabs для отступов»)
## Запреты
- (укажите запреты, например: «Не трогать CI/CD конфигурацию»)
## Конвенции проекта
- (укажите конвенции, например: «Имена классов в PascalCase, функции в snake_case»)
+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 | ... | ... | ... | ... |
+35
View File
@@ -0,0 +1,35 @@
# Session Summary
**Session:** {{ session_id }}
**Target:** {{ target_repo }}
**Depth:** {{ depth }}
**Date:** {{ date }}
## Configuration
| Функция | Статус |
|---|---|
| ADR | {{ adr_enabled }} |
| Alternative Architecture | {{ alt_arch_enabled }} |
| Red Team | {{ red_team_enabled }} |
| Risk Register | {{ risk_register_enabled }} |
| Invariant Tests | {{ invariant_tests_enabled }} |
| Layer Structure | {{ layer_structure_enabled }} |
## Phase Status
| Phase | Status |
|---|---|
| ANALYSIS | {{ analysis_status }} |
| DESIGN | {{ design_status }} |
| RED_TEAM | {{ red_team_status }} |
| DECOMPOSITION | {{ decomposition_status }} |
| SETUP | {{ setup_status }} |
| HANDOFF | {{ handoff_status }} |
## Quick Links
- Task Manifest: `.agent/task-manifest.json`
- Handoff Summary: `.agent/handoff-summary.md`
- Design Report: `.agent/analysis-report.md`
- ADR: `.agent/layer-1/adr/` (если есть)
+23
View File
@@ -0,0 +1,23 @@
{
"$schema": "metaagent-task-manifest",
"version": "1.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|docs",
"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 @@
1.1.0
+356
View File
@@ -0,0 +1,356 @@
# WORKFLOW — Сквозной пример сессии
---
## Сценарий A: Existing проект
**Цель:** Добавить в существующий FastAPI-проект ручку GET /health с тестами.
**Целевой репозиторий:** `github.com/example/fastapi-app`
**Пользователь:** "Добавь health-check endpoint и тесты к нему"
---
### Фаза INIT
Мета-агент читает `.agent/metaagent-request.md`, клонирует репозиторий, создаёт `.agent/`, пишет начальный чекпоинт:
```json
{
"metaagent_version": "1.1.0",
"session_id": "ses_abc123",
"target_repo": "/tmp/fastapi-app",
"goal": "Добавить GET /health с тестами",
"project_type": "existing",
"config": {
"depth": 6,
"design": { "adr": false, "alternative_arch": false },
"red_team": false,
"risk_register": false,
"decomposition": { "invariant_tests": false },
"handoff": { "layer_structure": false }
},
"phases": {
"analysis": "pending",
"design": "pending",
"red_team": "pending",
"decomposition": "pending",
"environment": "pending",
"handoff": "pending"
},
"tasks": [],
"last_updated": "2026-07-12T15:00:00Z"
}
```
---
### Фаза ANALYSE
Мета-агент выполняет `PROTOCOLS/01_ANALYSIS.md`. Определяет тип проекта: `existing`.
Результат `.agent/analysis-report.md`:
```markdown
## 2. Стек технологий
| Язык | Python 3.12 |
| Фреймворк | FastAPI |
| Тестовый раннер | pytest + httpx |
| Пакетный менеджер | pip + requirements.txt |
## 3. Архитектура
├── app/
│ ├── main.py
│ ├── routers/
│ │ └── users.py
│ ├── models/
│ │ └── user.py
│ └── schemas/
│ └── user.py
├── tests/
│ └── test_users.py
```
Тесты запущены: **12 passed, 0 failed**.
Чекпоинт обновлён: `analysis = "completed"`, `project_type = "existing"`.
Фаза DESIGN пропускается.
---
### Фаза DECOMPOSITION
Мета-агент выполняет `PROTOCOLS/03_DECOMPOSITION.md`.
Декомпозиция цели "Добавить GET /health с тестами":
| ID | Задача | Тип | Зависит от | AC |
|---|---|---|---|---|
| T1 | Создать health-check router | feature | — | Ручка возвращает 200 + {"status":"ok"} |
| T2 | Подключить router в main.py | config | T1 | Ручка доступна по /health |
| T3 | Написать тесты для /health | test | T2 | Тесты проверяют 200 и структуру ответа |
Создан `.agent/task-manifest.json` и `.agent/task-manifest.md`.
Чекпоинт обновлён: `decomposition = "completed"`. Tasks: T1-T3 со статусом `pending`.
---
### Фаза SETUP
Мета-агент выполняет `PROTOCOLS/04_ENVIRONMENT_SETUP.md` (ветка A: existing).
- `pip install -r requirements.txt` — OK
- Запуск pytest — OK, 12 passed (базовый тест)
- Результат в `.agent/baseline-test-report.log`
Чекпоинт обновлён: `environment = "completed"`.
---
### Фаза HANDOFF
Мета-агент выполняет `PROTOCOLS/05_HANDOFF.md`.
Создан `.agent/handoff-summary.md`:
```markdown
## Next Steps
Исполнительный агент начинает с задачи T1: "Создать health-check router".
## Caveats
- Придерживаться стиля существующего роутера users.py
- Не менять существующие тесты
- Убедиться, что response model соответствует JSON: {"status": "ok"}
```
Чекпоинт финализирован:
```json
{
"metaagent_version": "1.1.0",
"session_id": "ses_abc123",
"goal": "Добавить GET /health с тестами",
"project_type": "existing",
"config": {
"depth": 6,
"design": { "adr": false, "alternative_arch": false },
"red_team": false,
"risk_register": false,
"decomposition": { "invariant_tests": false },
"handoff": { "layer_structure": false }
},
"phases": {
"analysis": "completed",
"design": "skipped",
"red_team": "skipped",
"decomposition": "completed",
"environment": "completed",
"handoff": "completed"
},
"tasks": [
{ "id": "T1", "title": "Создать health-check router", "status": "pending" },
{ "id": "T2", "title": "Подключить router в main.py", "status": "pending" },
{ "id": "T3", "title": "Написать тесты для /health", "status": "pending" }
],
"last_updated": "2026-07-12T15:15:00Z"
}
```
Сигнал пользователю:
```
HANDOFF COMPLETE
Session: ses_abc123
Target: /tmp/fastapi-app
Type: existing
Tasks: 3 tasks ready
Исполнительный агент может начинать с задачи T1.
```
---
## Сценарий B: Greenfield проект (Cashflow Forecasting)
**Цель:** Спроектировать и реализовать MVP сервиса прогнозирования денежных потоков.
**Целевой репозиторий:** `github.com/example/cashflow-app`
**README:** README содержит описание:
> Сервис для прогнозирования движения денежных средств (cashflow forecasting).
> Пользователь загружает CSV с транзакциями, сервис строит прогноз на N дней вперёд.
> Стек: Python, FastAPI, SQLite, matplotlib для графиков.
---
### Фаза INIT
```json
{
"metaagent_version": "1.1.0",
"session_id": "ses_def456",
"target_repo": "/tmp/cashflow-app",
"goal": "Спроектировать и реализовать MVP сервиса прогнозирования денежных потоков",
"project_type": "greenfield",
"config": {
"depth": 7,
"design": { "adr": true, "alternative_arch": true },
"red_team": false,
"risk_register": true,
"decomposition": { "invariant_tests": true },
"handoff": { "layer_structure": true }
},
"phases": {
"analysis": "pending",
"design": "pending",
"red_team": "pending",
"decomposition": "pending",
"environment": "pending",
"handoff": "pending"
},
"tasks": [],
"last_updated": "2026-07-12T16:00:00Z"
}
```
---
### Фаза ANALYSE
Мета-агент выполняет `PROTOCOLS/01_ANALYSIS.md`. Определяет тип проекта: `greenfield`.
Сканирование корня: пусто (кроме README.md, LICENSE, .gitignore).
Извлечение требований из README:
| Тип | Требование |
|---|---|
| Функциональное | Загрузка CSV с транзакциями |
| Функциональное | Прогноз на N дней вперёд |
| Нефункциональное | Python, FastAPI |
| Нефункциональное | SQLite |
| Нефункциональное | matplotlib для графиков |
Чекпоинт: `analysis = "completed"`, `project_type = "greenfield"`.
Так как проект greenfield — мета-агент переходит к фазе DESIGN.
---
### Фаза DESIGN
Мета-агент выполняет `PROTOCOLS/02_DESIGN.md`.
Результат `.agent/design-report.md`:
```markdown
## 1. Технологический стек
| Язык | Python 3.12 |
| Фреймворк | FastAPI + Pydantic |
| БД | SQLite + SQLAlchemy |
| Визуализация | matplotlib |
| Тесты | pytest |
## 2. Архитектура
[Client] → HTTP → [FastAPI] → [CashflowService] → [SQLite]
[ForecastEngine] → [matplotlib]
## 3. Модули
| Модуль | Ответственность |
|---|---|
| app/main.py | Точка входа, роуты |
| app/models/transaction.py | Модель транзакции |
| app/services/cashflow.py | Бизнес-логика |
| app/services/forecast.py | Алгоритм прогноза |
| app/services/upload.py | Парсинг CSV |
| app/schemas/ | Pydantic схемы |
## 4. Модели
Transaction: id, date, amount, category, description
## 5. API
POST /upload — загрузить CSV
GET /forecast?days=30 — прогноз + график
## 6. Задачи (pre-grouped)
T1: init — проект, зависимости, scaffold
T2: models — модели + миграции
T3: upload — загрузка CSV
T4: forecast — алгоритм прогноза
T5: API — endpoints
T6: tests — тесты
```
Чекпоинт: `design = "completed"`.
---
### Фаза DECOMPOSITION
Мета-агент выполняет `PROTOCOLS/03_DECOMPOSITION.md`, используя design-report.
Итоговые задачи:
| ID | Задача | Тип | Зависит от |
|---|---|---|---|
| T1 | Инициализация проекта + зависимости | config | — |
| T2 | Модель Transaction + SQLAlchemy + SQLite | feature | T1 |
| T3 | Сервис загрузки и парсинга CSV | feature | T2 |
| T4 | ForecastEngine — алгоритм прогноза | feature | T2 |
| T5 | API endpoints + документация | feature | T3, T4 |
| T6 | Тесты (unit + integration) | test | T5 |
---
### Фаза SETUP
Мета-агент выполняет `PROTOCOLS/04_ENVIRONMENT_SETUP.md` (ветка B: greenfield).
- `poetry init` + создание pyproject.toml
- Установка fastapi, uvicorn, sqlalchemy, matplotlib, pytest
- Создание scaffold-структуры: `app/models/`, `app/services/`, `app/schemas/`, `tests/`
- Пустые заглушки модулей
- `.agent/baseline-test-report.log`: "0 tests — greenfield, scaffold готов"
---
### Фаза HANDOFF
```markdown
HANDOFF COMPLETE
Session: ses_def456
Target: /tmp/cashflow-app
Type: greenfield
Config: depth=7, adr=yes, risk_register=yes, invariant_tests=yes
Tasks: 6 tasks ready
Исполнительный агент может начинать с задачи T1 (init).
Архитектурный план: .agent/design-report.md
ADR: .agent/layer-1/adr/
```
---
## После HANDOFF: работа исполнительного агента
Исполнительный агент читает `.agent/handoff-summary.md`, `.agent/task-manifest.json`, выполняет задачи по порядку, обновляя checkpoints.json после каждой.
После завершения всех задач:
```
ALL TASKS COMPLETE
Session: ses_def456
Tasks: 6/6 completed
T1: Инициализация проекта ✓
T2: Модель Transaction ✓
T3: Сервис загрузки CSV ✓
T4: ForecastEngine ✓
T5: API endpoints ✓
T6: Тесты ✓
Все тесты проходят: 24/24 passed.
```
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env pwsh
# MetaAgent — установка исходников в целевой проект
# Usage: .\install.ps1 [[-Path] target_path] [-Update]
param(
[string]$Path = "",
[switch]$Update,
[switch]$Help
)
$MetaAgentSrc = Split-Path -Parent $MyInvocation.MyCommand.Path
function Show-Usage {
@"
Usage: install.ps1 [[-Path] target_path] [-Update] [-Help]
Install MetaAgent sources into <target>/.agent/src/
Options:
-Path Path to target project (default: interactive prompt)
-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 -Update
"@
exit 0
}
if ($Help) { Show-Usage }
$TargetPath = $Path
if (-not $TargetPath) {
$TargetPath = Read-Host "Enter path to target project"
}
$TargetPath = $TargetPath.Trim()
if (-not (Test-Path $TargetPath -PathType Container)) {
Write-Error "Directory '$TargetPath' does not exist."
exit 1
}
$TargetPath = (Resolve-Path $TargetPath).Path
$AgentDir = Join-Path $TargetPath ".agent"
$SrcDir = Join-Path $AgentDir "src"
$RulesDir = Join-Path $AgentDir "rules"
$ArchiveDir = Join-Path $AgentDir "archive"
$VersionFile = Join-Path $MetaAgentSrc "VERSION"
$Version = if (Test-Path $VersionFile) { Get-Content $VersionFile -Raw | ForEach-Object { $_.Trim() } } else { "?" }
New-Item -ItemType Directory -Path $SrcDir -Force | Out-Null
New-Item -ItemType Directory -Path $RulesDir -Force | Out-Null
New-Item -ItemType Directory -Path $ArchiveDir -Force | Out-Null
Write-Host "Installing MetaAgent v$Version$SrcDir"
# --- copy files ---
function Copy-File {
param([string]$Src, [string]$DstDir)
$name = Split-Path $Src -Leaf
if (-not (Test-Path $Src -PathType Leaf)) {
Write-Host " [skip] $name (not found)"
return
}
$dst = Join-Path $DstDir $name
if ($Update -or -not (Test-Path $dst)) {
Copy-Item $Src $dst -Force
Write-Host " [copy] $name"
} else {
Write-Host " [skip] $name (exists, use -Update to overwrite)"
}
}
function Copy-Dir {
param([string]$Src, [string]$DstDir)
$name = Split-Path $Src -Leaf
if (-not (Test-Path $Src -PathType Container)) {
Write-Host " [skip] $name/ (not found)"
return
}
$dst = Join-Path $DstDir $name
New-Item -ItemType Directory -Path $dst -Force | Out-Null
if ($Update) {
Get-ChildItem $Src | ForEach-Object {
Copy-Item $_.FullName $dst -Recurse -Force
}
} else {
Get-ChildItem $Src | ForEach-Object {
$targetPath = Join-Path $dst $_.Name
if (-not (Test-Path $targetPath)) {
Copy-Item $_.FullName $dst -Recurse
}
}
}
Write-Host " [copy] $name/"
}
Copy-File (Join-Path $MetaAgentSrc "META_AGENT_GUIDE.md") $SrcDir
Copy-File (Join-Path $MetaAgentSrc "BOUNDARIES.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 "TEMPLATES") $SrcDir
Copy-File (Join-Path $MetaAgentSrc "install.sh") $SrcDir
Copy-File (Join-Path $MetaAgentSrc "install.ps1") $SrcDir
# --- create / update AGENTS.md in root of target ---
$AgentsMd = Join-Path $TargetPath "AGENTS.md"
function New-AgentsMd {
param([string]$Path)
@"
# MetaAgent
Этот проект использует [MetaAgent](.agent/src/META_AGENT_GUIDE.md) v$Version
набор инструкций для AI-агента.
## Контекст MetaAgent
| Ресурс | Путь |
|--------|------|
| Главная инструкция | `.agent/src/META_AGENT_GUIDE.md` |
| Протоколы фаз | `.agent/src/PROTOCOLS/` |
| Шаблоны артефактов | `.agent/src/TEMPLATES/` |
| Границы (что разрешено/запрещено) | `.agent/src/BOUNDARIES.md` |
| Правила проекта | `.agent/rules/project-rules.md` |
| Примеры работы | `.agent/src/WORKFLOW.md` |
| Версия | `.agent/src/VERSION` |
## Состояние сессии (если инициализировано)
| Артефакт | Путь |
|----------|------|
| Чекпоинты сессии | `.agent/checkpoints.json` |
| Манифест задач | `.agent/task-manifest.json` |
| Сводка для exec-агента | `.agent/handoff-summary.md` |
| Анализ репозитория | `.agent/analysis-report.md` |
## Для исполнительного агента
1. **Прочитай** `.agent/src/META_AGENT_GUIDE.md` пойми жизненный цикл MetaAgent.
2. **Прочитай** `.agent/src/BOUNDARIES.md` соблюдай границы.
3. **Прочитай** `.agent/rules/project-rules.md` выполни пользовательские правила.
4. **Проверь** `.agent/checkpoints.json` если существует, используй как состояние сессии.
5. **Проверь** `.agent/task-manifest.json` если существует, выполняй задачи по порядку.
6. Если `.agent/` не инициализирован или устарел запусти `install.ps1 -Update` для
обновления исходников MetaAgent до актуальной версии.
"@
}
if (-not (Test-Path $AgentsMd -PathType Leaf)) {
New-AgentsMd $AgentsMd | Out-File -FilePath $AgentsMd -Encoding utf8
Write-Host " [create] AGENTS.md"
} elseif ($Update) {
New-AgentsMd $AgentsMd | Out-File -FilePath $AgentsMd -Encoding utf8
Write-Host " [update] AGENTS.md"
} else {
Write-Host " [skip] AGENTS.md (exists, use -Update to overwrite)"
}
Write-Host ""
Write-Host "Done! MetaAgent v$Version installed at $SrcDir"
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env bash
# MetaAgent — установка исходников в целевой проект
# Usage: ./install.sh [--update] [target_path]
set -euo pipefail
METAAGENT_SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
usage() {
cat <<EOF
Usage: $0 [--update] [target_path]
Install MetaAgent sources into <target>/.agent/src/
Options:
--update, -u Overwrite existing files in .agent/src/
--help, -h Show this help
Examples:
$0
$0 /path/to/project
$0 --update /path/to/project
EOF
exit 0
}
UPDATE=false
TARGET_PATH=""
while [[ $# -gt 0 ]]; do
case "$1" in
--update|-u) UPDATE=true; shift ;;
--help|-h) usage ;;
--*) echo "Unknown option: $1"; usage ;;
*) TARGET_PATH="$1"; shift ;;
esac
done
if [[ -z "$TARGET_PATH" ]]; then
read -r -p "Enter path to target project: " TARGET_PATH
fi
TARGET_PATH="${TARGET_PATH/#\~/$HOME}"
TARGET_PATH="$(cd "$TARGET_PATH" 2>/dev/null && pwd)" || {
echo "Error: Directory '$TARGET_PATH' does not exist."
exit 1
}
AGENT_DIR="$TARGET_PATH/.agent"
SRC_DIR="$AGENT_DIR/src"
VERSION="$(cat "$METAAGENT_SRC/VERSION" 2>/dev/null || echo '?')"
RULES_DIR="$AGENT_DIR/rules"
ARCHIVE_DIR="$AGENT_DIR/archive"
mkdir -p "$SRC_DIR" "$RULES_DIR" "$ARCHIVE_DIR"
echo "Installing MetaAgent v$VERSION$SRC_DIR"
# --- copy files ---
copy_file() {
local src="$1" dst_dir="$2"
local name; name="$(basename "$src")"
if [[ ! -f "$src" ]]; then
echo " [skip] $name (not found)"
return
fi
if [[ "$UPDATE" == true ]] || [[ ! -f "$dst_dir/$name" ]]; then
cp "$src" "$dst_dir/$name"
echo " [copy] $name"
else
echo " [skip] $name (exists, use --update to overwrite)"
fi
}
copy_dir() {
local src="$1" dst_dir="$2"
local name; name="$(basename "$src")"
if [[ ! -d "$src" ]]; then
echo " [skip] $name/ (not found)"
return
fi
mkdir -p "$dst_dir/$name"
if [[ "$UPDATE" == true ]]; then
cp -rf "$src"/* "$dst_dir/$name/" 2>/dev/null || true
else
cp -rn "$src"/* "$dst_dir/$name/" 2>/dev/null || true
fi
echo " [copy] $name/"
}
copy_file "$METAAGENT_SRC/META_AGENT_GUIDE.md" "$SRC_DIR"
copy_file "$METAAGENT_SRC/BOUNDARIES.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/TEMPLATES" "$SRC_DIR"
copy_file "$METAAGENT_SRC/install.sh" "$SRC_DIR"
copy_file "$METAAGENT_SRC/install.ps1" "$SRC_DIR"
# --- create / update AGENTS.md in root of target ---
AGENTS_MD="$TARGET_PATH/AGENTS.md"
create_agents_md() {
cat > "$1" << AGENTS_EOF
# MetaAgent
Этот проект использует [MetaAgent](.agent/src/META_AGENT_GUIDE.md) v$VERSION —
набор инструкций для AI-агента.
## Контекст MetaAgent
| Ресурс | Путь |
|--------|------|
| Главная инструкция | \`.agent/src/META_AGENT_GUIDE.md\` |
| Протоколы фаз | \`.agent/src/PROTOCOLS/\` |
| Шаблоны артефактов | \`.agent/src/TEMPLATES/\` |
| Границы (что разрешено/запрещено) | \`.agent/src/BOUNDARIES.md\` |
| Правила проекта | \`.agent/rules/project-rules.md\` |
| Примеры работы | \`.agent/src/WORKFLOW.md\` |
| Версия | \`.agent/src/VERSION\` |
## Состояние сессии (если инициализировано)
| Артефакт | Путь |
|----------|------|
| Чекпоинты сессии | \`.agent/checkpoints.json\` |
| Манифест задач | \`.agent/task-manifest.json\` |
| Сводка для exec-агента | \`.agent/handoff-summary.md\` |
| Анализ репозитория | \`.agent/analysis-report.md\` |
## Для исполнительного агента
1. **Прочитай** \`.agent/src/META_AGENT_GUIDE.md\` — пойми жизненный цикл MetaAgent.
2. **Прочитай** \`.agent/src/BOUNDARIES.md\` — соблюдай границы.
3. **Прочитай** \`.agent/rules/project-rules.md\` — выполни пользовательские правила.
4. **Проверь** \`.agent/checkpoints.json\` — если существует, используй как состояние сессии.
5. **Проверь** \`.agent/task-manifest.json\` — если существует, выполняй задачи по порядку.
6. Если \`.agent/\` не инициализирован или устарел — запусти \`install.sh --update\` для
обновления исходников MetaAgent до актуальной версии.
AGENTS_EOF
}
if [[ ! -f "$AGENTS_MD" ]]; then
create_agents_md "$AGENTS_MD"
echo " [create] AGENTS.md"
elif [[ "$UPDATE" == true ]]; then
create_agents_md "$AGENTS_MD"
echo " [update] AGENTS.md"
else
echo " [skip] AGENTS.md (exists, use --update to overwrite)"
fi
echo ""
echo "Done! MetaAgent v$VERSION installed at $SRC_DIR"
+106
View File
@@ -0,0 +1,106 @@
{
"$schema": "metaagent-task-manifest",
"version": "1.0",
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"goal": "Проанализировать существующий Obsidian-плагин zeroq-qol-modules (v1.1.4) и подготовить манифест задач для исполнительного агента.",
"created_at": "2026-07-22T00:00:00.000Z",
"tasks": [
{
"id": "T1",
"title": "Настроить ESLint и Prettier",
"description": "Добавить конфигурацию ESLint (typescript-eslint) и Prettier для единого стиля кода. Настроить скрипты lint/format в package.json.",
"type": "config",
"files": [
"package.json",
".eslintrc.json",
".prettierrc.json"
],
"depends_on": [],
"acceptance_criteria": [
"ESLint настроен с typescript-eslint, проходит без ошибок на src/",
"Prettier настроен с tabs, проходит без ошибок на src/",
"Скрипты lint и format добавлены в package.json"
],
"context": "В проекте используются tabs для отступов (см. review.md). Линтер/форматтер отсутствует.",
"status": "pending"
},
{
"id": "T2",
"title": "Написать тесты для BaseModule",
"description": "Добавить unit-тесты для BaseModule: проверка registerCleanup, onunload (очистка всех функций), defaultSettings.",
"type": "test",
"files": [
"src/modules/base-module.ts",
"tests/modules/base-module.test.ts",
"package.json",
"tsconfig.json"
],
"depends_on": [],
"acceptance_criteria": [
"Тест проверяет, что registerCleanup добавляет функцию в очередь",
"Тест проверяет, что onunload вызывает все зарегистрированные cleanup-функции",
"Тест проверяет, что onunload очищает очередь после выполнения",
"Тест проверяет, что defaultSettings возвращает пустой объект",
"Все тесты проходят (npm test)"
],
"context": "BaseModule — абстрактный класс-основа для всех модулей. Находится в src/modules/base-module.ts. Для тестов нужно настроить тестовый раннер (jest или vitest) и mock-объект Plugin из obsidian.",
"status": "pending"
},
{
"id": "T3",
"title": "Написать тесты для Attachment Clean Paste модуля",
"description": "Добавить unit-тесты для AttachmentCleanPasteModule: проверка shouldProcess (фильтрация расширений), обработка paste/drop событий.",
"type": "test",
"files": [
"src/modules/attachment-clean-paste/index.ts",
"tests/modules/attachment-clean-paste.test.ts"
],
"depends_on": ["T2"],
"acceptance_criteria": [
"Тест проверяет shouldProcess с разными расширениями и настройками",
"Тест проверяет, что обработчик paste вызывает preventDefault для файлов с подходящими расширениями",
"Тест проверяет, что модуль корректно создаёт ссылку [[path|name]]",
"Все тесты проходят (npm test)"
],
"context": "Attachment Clean Paste обрабатывает editor-paste и editor-drop события. Основная логика: shouldProcess (фильтрация по extensions) и processFile (копирование + создание ссылки).",
"status": "pending"
},
{
"id": "T4",
"title": "Написать тесты для Preserve Link Aliases модуля",
"description": "Добавить unit-тесты для PreserveLinkAliasesModule: проверка processContent (замена алиасов), getFilesToProcess (фильтрация файлов), обработка rename.",
"type": "test",
"files": [
"src/modules/preserve-link-aliases/index.ts",
"tests/modules/preserve-link-aliases.test.ts"
],
"depends_on": ["T2"],
"acceptance_criteria": [
"Тест проверяет processContent: замена алиаса при переименовании",
"Тест проверяет processContent: игнорирование ссылок без алиаса",
"Тест проверяет processContent: учёт флага processEmbeds",
"Тест проверяет getFilesToProcess: включение canvas-файлов при processCanvas=true",
"Все тесты проходят (npm test)"
],
"context": "Preserve Link Aliases обрабатывает vault rename. Основная логика: processContent (регулярное выражение для вики-ссылок) и getFilesToProcess (сбор markdown + опционально canvas).",
"status": "pending"
},
{
"id": "T5",
"title": "Добавить CI workflow для тестов и линтинга",
"description": "Добавить GitHub Actions workflow для автоматического запуска тестов и ESLint на push/PR в main/dev.",
"type": "config",
"files": [
".github/workflows/ci.yml"
],
"depends_on": ["T1", "T3", "T4"],
"acceptance_criteria": [
"Workflow запускается на push и pull_request в main и dev",
"Workflow выполняет npm ci, npm run lint, npm test",
"Workflow завершается с ошибкой при падении тестов или линтера"
],
"context": "Существующий release.yml в .github/workflows/ — можно использовать как шаблон. Node 20, ubuntu-latest.",
"status": "pending"
}
]
}
+128
View File
@@ -0,0 +1,128 @@
# Task Manifest
**Session:** a1b2c3d4-e5f6-7890-abcd-ef1234567890
**Goal:** Проанализировать существующий Obsidian-плагин zeroq-qol-modules (v1.1.4) и подготовить манифест задач для исполнительного агента.
**Date:** 2026-07-22T00:00:00.000Z
---
## Task Overview
| ID | Title | Type | Depends On | Status |
|---|---|---|---|---|
| T1 | Настроить ESLint и Prettier | config | — | pending |
| T2 | Написать тесты для BaseModule | test | — | pending |
| T3 | Написать тесты для Attachment Clean Paste модуля | test | T2 | pending |
| T4 | Написать тесты для Preserve Link Aliases модуля | test | T2 | pending |
| T5 | Добавить CI workflow для тестов и линтинга | config | T1, T3, T4 | pending |
**Total tasks:** 5
---
## Task Details
### T1: Настроить ESLint и Prettier
**Type:** config
**Description:** Добавить конфигурацию ESLint (typescript-eslint) и Prettier для единого стиля кода. Настроить скрипты lint/format в package.json.
**Files:**
- `package.json`
- `.eslintrc.json`
- `.prettierrc.json`
**Depends on:**
**Acceptance Criteria:**
- [ ] ESLint настроен с typescript-eslint, проходит без ошибок на src/
- [ ] Prettier настроен с tabs, проходит без ошибок на src/
- [ ] Скрипты lint и format добавлены в package.json
**Context:** В проекте используются tabs для отступов (см. review.md). Линтер/форматтер отсутствует.
---
### T2: Написать тесты для BaseModule
**Type:** test
**Description:** Добавить unit-тесты для BaseModule: проверка registerCleanup, onunload (очистка всех функций), defaultSettings.
**Files:**
- `src/modules/base-module.ts`
- `tests/modules/base-module.test.ts`
- `package.json`
- `tsconfig.json`
**Depends on:**
**Acceptance Criteria:**
- [ ] Тест проверяет, что registerCleanup добавляет функцию в очередь
- [ ] Тест проверяет, что onunload вызывает все зарегистрированные cleanup-функции
- [ ] Тест проверяет, что onunload очищает очередь после выполнения
- [ ] Тест проверяет, что defaultSettings возвращает пустой объект
- [ ] Все тесты проходят (npm test)
**Context:** BaseModule — абстрактный класс-основа для всех модулей. Находится в src/modules/base-module.ts. Для тестов нужно настроить тестовый раннер (jest или vitest) и mock-объект Plugin из obsidian.
---
### T3: Написать тесты для Attachment Clean Paste модуля
**Type:** test
**Description:** Добавить unit-тесты для AttachmentCleanPasteModule: проверка shouldProcess (фильтрация расширений), обработка paste/drop событий.
**Files:**
- `src/modules/attachment-clean-paste/index.ts`
- `tests/modules/attachment-clean-paste.test.ts`
**Depends on:** T2
**Acceptance Criteria:**
- [ ] Тест проверяет shouldProcess с разными расширениями и настройками
- [ ] Тест проверяет, что обработчик paste вызывает preventDefault для файлов с подходящими расширениями
- [ ] Тест проверяет, что модуль корректно создаёт ссылку [[path|name]]
- [ ] Все тесты проходят (npm test)
**Context:** Attachment Clean Paste обрабатывает editor-paste и editor-drop события. Основная логика: shouldProcess (фильтрация по extensions) и processFile (копирование + создание ссылки).
---
### T4: Написать тесты для Preserve Link Aliases модуля
**Type:** test
**Description:** Добавить unit-тесты для PreserveLinkAliasesModule: проверка processContent (замена алиасов), getFilesToProcess (фильтрация файлов), обработка rename.
**Files:**
- `src/modules/preserve-link-aliases/index.ts`
- `tests/modules/preserve-link-aliases.test.ts`
**Depends on:** T2
**Acceptance Criteria:**
- [ ] Тест проверяет processContent: замена алиаса при переименовании
- [ ] Тест проверяет processContent: игнорирование ссылок без алиаса
- [ ] Тест проверяет processContent: учёт флага processEmbeds
- [ ] Тест проверяет getFilesToProcess: включение canvas-файлов при processCanvas=true
- [ ] Все тесты проходят (npm test)
**Context:** Preserve Link Aliases обрабатывает vault rename. Основная логика: processContent (регулярное выражение для вики-ссылок) и getFilesToProcess (сбор markdown + опционально canvas).
---
### T5: Добавить CI workflow для тестов и линтинга
**Type:** config
**Description:** Добавить GitHub Actions workflow для автоматического запуска тестов и ESLint на push/PR в main/dev.
**Files:**
- `.github/workflows/ci.yml`
**Depends on:** T1, T3, T4
**Acceptance Criteria:**
- [ ] Workflow запускается на push и pull_request в main и dev
- [ ] Workflow выполняет npm ci, npm run lint, npm test
- [ ] Workflow завершается с ошибкой при падении тестов или линтера
**Context:** Существующий release.yml в .github/workflows/ — можно использовать как шаблон. Node 20, ubuntu-latest.
+42
View File
@@ -0,0 +1,42 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Verify version
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
MANIFEST_VERSION=$(node -p "require('./manifest.json').version")
if [ "$TAG_VERSION" != "$MANIFEST_VERSION" ]; then
echo "❌ Tag ($TAG_VERSION) != manifest ($MANIFEST_VERSION)"
exit 1
fi
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: ${{ github.ref_name }}
files: |
main.js
manifest.json
styles.css
+35
View File
@@ -0,0 +1,35 @@
# MetaAgent
Этот проект использует [MetaAgent](.agent/src/META_AGENT_GUIDE.md) v1.1.0 —
набор инструкций для AI-агента.
## Контекст MetaAgent
| Ресурс | Путь |
|--------|------|
| Главная инструкция | `.agent/src/META_AGENT_GUIDE.md` |
| Протоколы фаз | `.agent/src/PROTOCOLS/` |
| Шаблоны артефактов | `.agent/src/TEMPLATES/` |
| Границы (что разрешено/запрещено) | `.agent/src/BOUNDARIES.md` |
| Правила проекта | `.agent/rules/project-rules.md` |
| Примеры работы | `.agent/src/WORKFLOW.md` |
| Версия | `.agent/src/VERSION` |
## Состояние сессии (если инициализировано)
| Артефакт | Путь |
|----------|------|
| Чекпоинты сессии | `.agent/checkpoints.json` |
| Манифест задач | `.agent/task-manifest.json` |
| Сводка для exec-агента | `.agent/handoff-summary.md` |
| Анализ репозитория | `.agent/analysis-report.md` |
## Для исполнительного агента
1. **Прочитай** `.agent/src/META_AGENT_GUIDE.md` — пойми жизненный цикл MetaAgent.
2. **Прочитай** `.agent/src/BOUNDARIES.md` — соблюдай границы.
3. **Прочитай** `.agent/rules/project-rules.md` — выполни пользовательские правила.
4. **Проверь** `.agent/checkpoints.json` — если существует, используй как состояние сессии.
5. **Проверь** `.agent/task-manifest.json` — если существует, выполняй задачи по порядку.
6. Если `.agent/` не инициализирован или устарел — запусти `install.sh --update` для
обновления исходников MetaAgent до актуальной версии.
+30 -104
View File
@@ -1,115 +1,41 @@
# ZeroQ QoL Modules
Модульный плагин для **Obsidian** с набором QoL-функций, которые можно включать и отключать в настройках.
A modular **Obsidian** plugin with toggleable QoL (quality-of-life) modules. Each module can be independently enabled or disabled in the settings.
Modular **Obsidian** plugin with toggleable QoL modules in settings.
### Modules
## Установка / Installation
- **Attachment Clean Paste** — Replaces `![[embed]]` with `[[path/file|name]]` for specified file extensions on paste/drop
- **Preserve Link Aliases** — Preserves original link aliases when files are renamed
1. Скачайте `main.js`, `manifest.json` и `styles.css` из [релизов](https://github.com/your/repo/releases)
2. Поместите их в папку `.obsidian/plugins/zeroq-qol-modules/` вашего хранилища
3. В Obsidian: **Настройки****Community plugins****Включить**
4. Активируйте плагин **ZeroQ QoL Modules**
## Язык / Language
Язык плагина синхронизируется с **языком интерфейса Obsidian** (Settings → About → Language).
- Obsidian на русском → плагин на русском
- Obsidian на другом языке → плагин на английском
The plugin language syncs with **Obsidian's UI language** setting.
- Obsidian in English → plugin in English
- Obsidian in any other language → falls back to English
## Модули / Modules
- **Attachment Clean Paste** — Заменяет `![[embed]]` на `[[path/file|name]]` для файлов с указанными расширениями при вставке/перетаскивании
Каждый модуль включается/отключается в настройках плагина.
## Разработка / Development
### Архитектура / Architecture
```
src/
├── main.ts # точка входа, Plugin class
├── settings.ts # общая панель настроек
├── types.ts # базовые типы
├── locales/
│ ├── en.ts # тип Locale + EN-словарь
│ ├── ru.ts # RU-словарь
│ └── index.ts # авто-определение языка
└── modules/
├── index.ts # реестр модулей (статический импорт)
├── base-module.ts # абстрактный класс модуля
└── attachment-clean-paste/
└── index.ts # реализация модуля
```
**Принцип работы:**
- `main.ts` загружает настройки, итерирует `MODULES` и вызывает `onload()` для включённых
- Каждый модуль через `registerCleanup()` регистрирует функции очистки, которые вызываются при выключении модуля или выгрузке плагина
- В настройках отображаются все модули с чекбоксами вкл/выкл
### Добавление нового модуля / Adding a module
1. Создайте папку `src/modules/my-module/`
2. Создайте класс, унаследованный от `BaseModule`:
```typescript
import { BaseModule } from '../base-module';
import { locale } from '../../locales';
export class MyModule extends BaseModule {
id = 'my-module';
name = locale.modules['my-module'].name;
description = locale.modules['my-module'].description;
get defaultSettings() {
return { /* настройки по умолчанию */ };
}
onload(plugin, moduleSettings) {
// инициализация: регистрация событий, команд, настроек
}
onunload(plugin) {
// очистка (cleanup происходит автоматически через registerCleanup)
}
}
```
3. Добавьте переводы в `src/locales/en.ts` и `src/locales/ru.ts`
4. Зарегистрируйте модуль в `src/modules/index.ts`:
```typescript
import { MyModule } from './my-module';
export const MODULES: QoLModule[] = [
new AttachmentCleanPasteModule(),
new MyModule(),
];
```
### Добавление нового языка / Adding a language
1. Создайте `src/locales/de.ts` со структурой `Locale`
2. Импортируйте и добавьте в словарь в `src/locales/index.ts`:
```typescript
import { de } from './de';
const locales: Record<string, Locale> = { en, ru, de };
```
### Сборка / Build
### Build
```bash
npm run build # production-сборка
npm run dev # dev-сборка с sourcemap
npm run build # production
npm run dev # dev with sourcemaps
```
## Лицензия / License
### License
MIT
---
# ZeroQ QoL Modules
Модульный плагин для **Obsidian** с набором QoL-функций, которые можно независимо включать и отключать в настройках.
### Модули
- **Attachment Clean Paste** — заменяет `![[вложение]]` на `[[путь/файл|имя]]` для указанных расширений при вставке/перетаскивании
- **Preserve Link Aliases** — сохраняет оригинальные алиасы ссылок при переименовании файлов
### Сборка
```bash
npm run build # production
npm run dev # dev со sourcemap
```
### Лицензия
MIT
+6
View File
@@ -1,5 +1,6 @@
import esbuild from 'esbuild';
import process from 'process';
import fs from 'fs';
import builtins from 'builtin-modules';
const banner = `/*
@@ -9,6 +10,11 @@ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
const prod = process.argv[2] === 'production';
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
const manifest = JSON.parse(fs.readFileSync('./manifest.json', 'utf8'));
manifest.version = pkg.version;
fs.writeFileSync('./manifest.json', JSON.stringify(manifest, null, '\t') + '\n');
esbuild
.build({
banner: { js: banner },
+451
View File
@@ -0,0 +1,451 @@
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
*/
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/main.ts
var main_exports = {};
__export(main_exports, {
default: () => ZeroQoLModulesPlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian4 = require("obsidian");
// src/modules/attachment-clean-paste/index.ts
var import_obsidian = require("obsidian");
// src/modules/base-module.ts
var BaseModule = class {
constructor() {
this.cleanupFns = [];
}
get defaultSettings() {
return {};
}
onunload(_plugin) {
for (const fn of this.cleanupFns) {
fn();
}
this.cleanupFns = [];
}
renderInlineSettings(_containerEl, _settings, _saveSettings) {
}
registerCleanup(fn) {
this.cleanupFns.push(fn);
}
};
// src/locales/en.ts
var en = {
settings: {
title: "ZeroQ QoL Modules",
description: "Enable or disable modules as you wish."
},
modules: {
"attachment-clean-paste": {
name: "Attachment Clean Paste",
description: "Replaces ![[embed]] with [[path/file|name]] for configured file extensions on paste/drop",
descriptionShort: "Replaces ![[embed]] with [[path/file|name]] for specified file extensions.",
extensionsLabel: "File extensions",
extensionsDesc: "Comma-separated extensions (e.g.: png, jpg, pdf, mp3). Files with these extensions will be inserted as [[path/file|name]] instead of ![[path/file]].",
extensionsPlaceholder: "png, jpg, jpeg, gif, svg, webp, pdf"
},
"preserve-link-aliases": {
name: "Preserve Link Aliases",
description: "Preserves original link aliases when attachments are renamed",
processEmbedsLabel: "Process embed links",
processEmbedsDesc: "Also process ![[embed]] links (disabled by default)",
processCanvasLabel: "Process Canvas files",
processCanvasDesc: "Also process links in Canvas (.canvas) files"
}
}
};
// src/locales/ru.ts
var ru = {
settings: {
title: "ZeroQ QoL Modules",
description: "\u0412\u043A\u043B\u044E\u0447\u0430\u0439\u0442\u0435 \u0438 \u043E\u0442\u043A\u043B\u044E\u0447\u0430\u0439\u0442\u0435 \u043C\u043E\u0434\u0443\u043B\u0438 \u043F\u043E \u0441\u0432\u043E\u0435\u043C\u0443 \u0443\u0441\u043C\u043E\u0442\u0440\u0435\u043D\u0438\u044E."
},
modules: {
"attachment-clean-paste": {
name: "Attachment Clean Paste",
description: "\u0417\u0430\u043C\u0435\u043D\u044F\u0435\u0442 ![[\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0435]] \u043D\u0430 [[\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0435|\u0438\u043C\u044F]] \u0434\u043B\u044F \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u044B\u0445 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u0439 \u043F\u0440\u0438 \u0432\u0441\u0442\u0430\u0432\u043A\u0435/\u043F\u0435\u0440\u0435\u0442\u0430\u0441\u043A\u0438\u0432\u0430\u043D\u0438\u0438",
descriptionShort: "\u0417\u0430\u043C\u0435\u043D\u044F\u0435\u0442 ![[\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0435]] \u043D\u0430 [[\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0435|\u0438\u043C\u044F]] \u0434\u043B\u044F \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u044B\u0445 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u0439.",
extensionsLabel: "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F \u0444\u0430\u0439\u043B\u043E\u0432",
extensionsDesc: "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: png, jpg, pdf, mp3). \u0424\u0430\u0439\u043B\u044B \u0441 \u044D\u0442\u0438\u043C\u0438 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F\u043C\u0438 \u0431\u0443\u0434\u0443\u0442 \u0432\u0441\u0442\u0430\u0432\u043B\u044F\u0442\u044C\u0441\u044F \u043A\u0430\u043A [[path/file|name]] \u0432\u043C\u0435\u0441\u0442\u043E ![[path/file]].",
extensionsPlaceholder: "png, jpg, jpeg, gif, svg, webp, pdf"
},
"preserve-link-aliases": {
name: "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0435 \u0430\u043B\u0438\u0430\u0441\u043E\u0432 \u0441\u0441\u044B\u043B\u043E\u043A",
description: "\u0421\u043E\u0445\u0440\u0430\u043D\u044F\u0435\u0442 \u043E\u0440\u0438\u0433\u0438\u043D\u0430\u043B\u044C\u043D\u044B\u0435 \u0430\u043B\u0438\u0430\u0441\u044B \u0441\u0441\u044B\u043B\u043E\u043A \u043F\u0440\u0438 \u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u0438\u0438 \u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0439",
processEmbedsLabel: "\u041E\u0431\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0442\u044C embed-\u0441\u0441\u044B\u043B\u043A\u0438",
processEmbedsDesc: "\u0422\u0430\u043A\u0436\u0435 \u043E\u0431\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0442\u044C ![[embed]] \u0441\u0441\u044B\u043B\u043A\u0438 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E)",
processCanvasLabel: "\u041E\u0431\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0442\u044C Canvas-\u0444\u0430\u0439\u043B\u044B",
processCanvasDesc: "\u0422\u0430\u043A\u0436\u0435 \u043E\u0431\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0438 \u0432 Canvas (.canvas) \u0444\u0430\u0439\u043B\u0430\u0445"
}
}
};
// src/locales/index.ts
var locales = { en, ru };
function detectLocale() {
const lang = (document.documentElement.lang || "en").slice(0, 2);
return locales[lang] || en;
}
var locale = detectLocale();
// src/modules/attachment-clean-paste/index.ts
var MODULE_ID = "attachment-clean-paste";
var AttachmentCleanPasteModule = class extends BaseModule {
constructor() {
super(...arguments);
this.id = MODULE_ID;
this.name = locale.modules["attachment-clean-paste"].name;
this.description = locale.modules["attachment-clean-paste"].description;
}
get defaultSettings() {
return {
extensions: "png, jpg, jpeg, gif, svg, webp, bmp, pdf"
};
}
onload(plugin, moduleSettings) {
this.app = plugin.app;
const rawSettings = moduleSettings;
const pasteRef = plugin.app.workspace.on(
"editor-paste",
this.createPasteHandler(rawSettings)
);
this.registerCleanup(() => plugin.app.workspace.offref(pasteRef));
const dropRef = plugin.app.workspace.on(
"editor-drop",
this.createDropHandler(rawSettings)
);
this.registerCleanup(() => plugin.app.workspace.offref(dropRef));
}
renderInlineSettings(containerEl, settings, saveSettings) {
const loc = locale.modules["attachment-clean-paste"];
const extSettings = settings;
new import_obsidian.Setting(containerEl).setName(loc.extensionsLabel).setDesc(loc.extensionsDesc).addTextArea((textarea) => {
var _a;
textarea.setPlaceholder(loc.extensionsPlaceholder).setValue((_a = extSettings == null ? void 0 : extSettings.extensions) != null ? _a : "").onChange(async (value) => {
settings.extensions = value;
await saveSettings();
});
textarea.inputEl.rows = 4;
textarea.inputEl.style.width = "100%";
});
}
async processAll(files, editor, settings, evt) {
const matchingFiles = Array.from(files).filter(
(f) => this.shouldProcess(f.name, settings)
);
if (matchingFiles.length === 0)
return;
evt.preventDefault();
evt.stopPropagation();
const links = [];
for (const file of matchingFiles) {
const link = await this.processFile(file, settings);
if (link)
links.push(link);
}
if (links.length === 0)
return;
editor.replaceSelection(links.join("\n\n"));
}
createPasteHandler(settings) {
return async (evt, editor, _info) => {
var _a;
const files = (_a = evt.clipboardData) == null ? void 0 : _a.files;
if (!files || files.length === 0)
return;
await this.processAll(files, editor, settings, evt);
};
}
createDropHandler(settings) {
return async (evt, editor, _info) => {
var _a;
const files = (_a = evt.dataTransfer) == null ? void 0 : _a.files;
if (!files || files.length === 0)
return;
await this.processAll(files, editor, settings, evt);
};
}
shouldProcess(filename, settings) {
const dotIndex = filename.lastIndexOf(".");
if (dotIndex === -1)
return false;
const ext = filename.slice(dotIndex + 1).toLowerCase();
const exts = settings.extensions.split(",").map((e) => e.trim().toLowerCase()).filter((e) => e.length > 0);
return exts.some((e) => {
const cleanExt = e.startsWith(".") ? e.slice(1) : e;
return cleanExt === ext;
});
}
async processFile(file, _settings) {
try {
const arrayBuffer = await file.arrayBuffer();
const filename = file.name;
const nameWithoutExt = filename.replace(/\.[^/.]+$/, "");
let attachmentFolder = this.app.vault.getConfig(
"attachmentFolderPath"
);
if (!attachmentFolder)
attachmentFolder = "attachments";
const folderPath = (0, import_obsidian.normalizePath)(attachmentFolder);
if (!await this.app.vault.adapter.exists(folderPath)) {
await this.app.vault.createFolder(folderPath);
}
const ext = filename.slice(filename.lastIndexOf(".") + 1);
const basePath = (0, import_obsidian.normalizePath)(`${folderPath}/${nameWithoutExt}`);
const availablePath = await this.app.vault.getAvailablePath(
basePath,
ext
);
await this.app.vault.createBinary(availablePath, arrayBuffer);
return `[[${availablePath}|${nameWithoutExt}]]`;
} catch (e) {
new import_obsidian.Notice(`Clean Paste error: ${e.message}`);
console.error("Attachment Clean Paste:", e);
}
return null;
}
};
// src/modules/preserve-link-aliases/index.ts
var import_obsidian2 = require("obsidian");
var MODULE_ID2 = "preserve-link-aliases";
var PreserveLinkAliasesModule = class extends BaseModule {
constructor() {
super(...arguments);
this.id = MODULE_ID2;
this.name = locale.modules["preserve-link-aliases"].name;
this.description = locale.modules["preserve-link-aliases"].description;
}
get defaultSettings() {
return {
processEmbeds: false,
processCanvas: false
};
}
onload(plugin, moduleSettings) {
this.vault = plugin.app.vault;
const rawSettings = moduleSettings;
const renameRef = plugin.app.vault.on(
"rename",
(file, oldPath) => this.handleRename(file, oldPath, rawSettings)
);
this.registerCleanup(() => plugin.app.vault.offref(renameRef));
}
renderInlineSettings(containerEl, settings, saveSettings) {
const loc = locale.modules["preserve-link-aliases"];
const s = settings;
new import_obsidian2.Setting(containerEl).setName(loc.processEmbedsLabel).setDesc(loc.processEmbedsDesc).addToggle(
(toggle) => toggle.setValue(s.processEmbeds).onChange(async (value) => {
s.processEmbeds = value;
await saveSettings();
})
);
new import_obsidian2.Setting(containerEl).setName(loc.processCanvasLabel).setDesc(loc.processCanvasDesc).addToggle(
(toggle) => toggle.setValue(s.processCanvas).onChange(async (value) => {
s.processCanvas = value;
await saveSettings();
})
);
}
async handleRename(file, oldPath, settings) {
var _a;
const oldName = (_a = oldPath.split("/").pop()) != null ? _a : "";
const newName = file.name;
const oldBasename = oldName.replace(/\.[^/.]+$/, "");
const newBasename = newName.replace(/\.[^/.]+$/, "");
if (oldBasename === newBasename)
return;
const files = this.getFilesToProcess(settings);
for (const f of files) {
const content = await this.vault.read(f);
const updated = this.processContent(content, newName, newBasename, oldBasename, settings);
if (updated !== content) {
await this.vault.modify(f, updated);
}
}
}
getFilesToProcess(settings) {
const files = [];
for (const file of this.vault.getMarkdownFiles()) {
files.push(file);
}
if (settings.processCanvas) {
const abstractFiles = this.vault.getFiles();
for (const file of abstractFiles) {
if (file.extension === "canvas") {
files.push(file);
}
}
}
return files;
}
processContent(content, newFileName, newBasename, oldBasename, settings) {
const wikiLinkRe = /(!?)\[\[([^\]]*?)]]/g;
return content.replace(wikiLinkRe, (fullMatch, prefix, inner) => {
if (prefix === "!" && !settings.processEmbeds) {
return fullMatch;
}
const pipeIndex = inner.indexOf("|");
if (pipeIndex === -1)
return fullMatch;
const linkPath = inner.slice(0, pipeIndex);
const alias = inner.slice(pipeIndex + 1);
if (alias !== newBasename)
return fullMatch;
const linkFilename = linkPath.split("/").pop();
if (linkFilename !== newFileName)
return fullMatch;
return `${prefix}[[${linkPath}|${oldBasename}]]`;
});
}
};
// src/modules/index.ts
var MODULES = [
new AttachmentCleanPasteModule(),
new PreserveLinkAliasesModule()
];
// src/settings.ts
var import_obsidian3 = require("obsidian");
var MODULE_DEFAULTS = MODULES.reduce(
(acc, mod) => {
acc[mod.id] = {
enabled: true,
settings: { ...mod.defaultSettings }
};
return acc;
},
{}
);
var DEFAULT_SETTINGS = {
modules: MODULE_DEFAULTS
};
function getModuleSettings(settings, moduleId) {
var _a, _b;
return (_b = (_a = settings.modules[moduleId]) == null ? void 0 : _a.settings) != null ? _b : {};
}
var ZeroQSettingTab = class extends import_obsidian3.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h1", { text: locale.settings.title });
containerEl.createEl("p", {
text: locale.settings.description,
attr: { style: "color: var(--text-muted); margin-bottom: 20px;" }
});
for (const module2 of MODULES) {
const moduleCfg = this.plugin.settings.modules[module2.id];
new import_obsidian3.Setting(containerEl).setName(module2.name).setDesc(module2.description).addToggle(
(toggle) => {
var _a;
return toggle.setValue((_a = moduleCfg == null ? void 0 : moduleCfg.enabled) != null ? _a : true).onChange(async (value) => {
if (!this.plugin.settings.modules[module2.id]) {
this.plugin.settings.modules[module2.id] = {
enabled: value,
settings: { ...module2.defaultSettings }
};
} else {
this.plugin.settings.modules[module2.id].enabled = value;
}
await this.plugin.saveSettings();
if (value) {
this.plugin.loadModule(module2);
} else {
this.plugin.unloadModule(module2);
}
});
}
);
if (moduleCfg) {
module2.renderInlineSettings(
containerEl,
moduleCfg.settings,
() => this.plugin.saveSettings()
);
}
}
}
};
// src/main.ts
var ZeroQoLModulesPlugin = class extends import_obsidian4.Plugin {
constructor() {
super(...arguments);
this.loadedModules = /* @__PURE__ */ new Set();
}
async onload() {
await this.loadSettings();
this.addSettingTab(new ZeroQSettingTab(this.app, this));
for (const module2 of MODULES) {
const moduleCfg = this.settings.modules[module2.id];
if (moduleCfg == null ? void 0 : moduleCfg.enabled) {
this.loadModule(module2);
}
}
}
onunload() {
for (const module2 of MODULES) {
this.unloadModule(module2);
}
}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
for (const module2 of MODULES) {
if (!this.settings.modules[module2.id]) {
this.settings.modules[module2.id] = {
enabled: true,
settings: { ...module2.defaultSettings }
};
}
}
}
async saveSettings() {
await this.saveData(this.settings);
}
loadModule(module2) {
if (this.loadedModules.has(module2.id))
return;
const moduleSettings = getModuleSettings(this.settings, module2.id);
module2.onload(this, moduleSettings);
this.loadedModules.add(module2.id);
}
unloadModule(module2) {
if (!this.loadedModules.has(module2.id))
return;
module2.onunload(this);
this.loadedModules.delete(module2.id);
}
};
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "zeroq-qol-modules",
"name": "ZeroQ QoL Modules",
"version": "1.0.3",
"version": "1.1.5",
"minAppVersion": "0.15.0",
"description": "QoL Modules",
"author": "oqyude",
+7 -3
View File
@@ -1,13 +1,17 @@
{
"name": "zeroq-qol-modules",
"version": "1.0.0",
"version": "1.1.5",
"description": "Modular Obsidian plugin with toggleable QoL modules",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "node esbuild.config.mjs production"
"build": "node esbuild.config.mjs production",
"release": "bash scripts/release.sh"
},
"keywords": ["obsidian", "plugin"],
"keywords": [
"obsidian",
"plugin"
],
"author": "",
"license": "MIT",
"devDependencies": {
+197
View File
@@ -0,0 +1,197 @@
# Project Review: zeroq-qol-modules
## Overview
Obsidian plugin with a **modular architecture**. Each feature is a separate module that can be toggled on/off in settings.
**Plugin ID:** `zeroq-qol-modules`
**Entry point:** `src/main.ts` → compiles to `main.js` (esbuild)
**Version source:** `package.json` → auto-synced to `manifest.json` on build
**Current modules (2):** `attachment-clean-paste`, `preserve-link-aliases`
## Stack
- **Language:** TypeScript (5.x)
- **Bundler:** esbuild (0.17.x)
- **Obsidian API:** `obsidian` latest (type defs only, excluded from bundle)
- **Format:** CommonJS (`format: 'cjs'`), target ES2018
## Directory Structure
```
src/
├── main.ts # Plugin class — loads settings, bootstraps modules
├── settings.ts # ZeroQSettingTab — single settings pane, renders all modules
├── types.ts # QoLModule interface, ZeroQSettings, ModuleSettings
├── locales/
│ ├── en.ts # Locale type definition + English dictionary
│ ├── ru.ts # Russian dictionary (same Locale shape)
│ └── index.ts # Auto-detects language from <html lang>, exports locale
└── modules/
├── index.ts # Static registry: array of all module instances
├── base-module.ts # BaseModule abstract class
├── attachment-clean-paste/
│ └── index.ts # Handles paste/drop — replaces ![[embed]] → [[path|name]]
└── preserve-link-aliases/
└── index.ts # Preserves aliases on vault rename events
```
## Architecture
### Plugin Lifecycle (`main.ts`)
```
onload()
→ loadSettings() # merge DEFAULT_SETTINGS + saved data
→ addSettingTab() # register ZeroQSettingTab
→ for each module:
if enabled → loadModule(module)
→ module.onload(this, moduleSettings)
onunload()
→ for each module:
unloadModule(module)
→ module.onunload(this)
```
### Module Lifecycle
Each module is a **singleton** implementing `QoLModule` (or extending `BaseModule`):
| Method | Purpose |
|--------|---------|
| `onload(plugin, moduleSettings)` | Initialize: register events, commands, setting tab |
| `onunload(plugin)` | Cleanup (auto-runs `registerCleanup` callbacks) |
| `renderInlineSettings(containerEl, settings, saveSettings)` | Render module-specific settings inline (no separate tab) |
| `defaultSettings` | Default module settings object |
### Cleanup pattern
Modules use `this.registerCleanup(fn)` to queue cleanup callbacks. They auto-run in `onunload()`. Example:
```ts
const ref = plugin.app.workspace.on('editor-paste', handler);
this.registerCleanup(() => plugin.app.workspace.offref(ref));
```
### Settings architecture
- **ZeroQSettingTab** (`settings.ts`) — single Obsidian settings pane
- Iterates `MODULES`, renders each with:
1. Toggle (enable/disable) — toggling calls `loadModule` / `unloadModule` at runtime
2. `module.renderInlineSettings()` — module-specific controls rendered directly below the toggle
- **No separate PluginSettingTab per module** — all in one block
### Settings data shape
```ts
interface ZeroQSettings {
modules: Record<string, {
enabled: boolean;
settings: Record<string, unknown>; // module-specific, typed per module
}>;
}
```
### Localization (`locales/`)
- **Locale type** defined in `en.ts` as the interface `Locale`
- **Language detection:** reads `<html lang>` attribute (set by Obsidian per its own language setting)
- Fallback to `'en'` if language is not in the dictionary
- Usage: `import { locale } from '../../locales'``locale.settings.title`, `locale.modules['xxx'].name`
**To add a language:**
1. Create `src/locales/de.ts` with `Locale` shape
2. Import it in `src/locales/index.ts` and add to `locales` record
## Adding a New Module
### Step 1: Create the module class
Extend `BaseModule` in `src/modules/my-module/index.ts`:
```ts
import { Plugin } from 'obsidian';
import { BaseModule } from '../base-module';
import { locale } from '../../locales';
export class MyModule extends BaseModule {
id = 'my-module';
name = locale.modules['my-module'].name;
description = locale.modules['my-module'].description;
get defaultSettings(): Record<string, unknown> {
return { myOption: 'default' };
}
onload(plugin: Plugin, moduleSettings: Record<string, unknown>): void {
// register events/commands via plugin.registerEvent / plugin.addCommand
// use this.registerCleanup(fn) for teardown
}
renderInlineSettings(
containerEl: HTMLElement,
settings: Record<string, unknown>,
saveSettings: () => Promise<void>,
): void {
// render controls into containerEl
// mutate settings object directly, then call saveSettings()
new Setting(containerEl)
.setName('My option')
.addText(text => text
.setValue(String(settings.myOption ?? ''))
.onChange(async v => {
settings.myOption = v;
await saveSettings();
}));
}
}
```
### Step 2: Add translations
In `src/locales/en.ts`, add to the `Locale` interface and the `en` object:
```ts
'modules': {
'my-module': {
name: string;
description: string;
// any other strings this module needs
};
}
```
Do the same in `src/locales/ru.ts`.
### Step 3: Register in the registry
In `src/modules/index.ts`:
```ts
import { MyModule } from './my-module';
export const MODULES: QoLModule[] = [
new AttachmentCleanPasteModule(),
new MyModule(),
];
```
## Build
```bash
npm run build # production build (minified, no sourcemaps)
npm run dev # dev build (inline sourcemaps)
```
`esbuild.config.mjs` also syncs `version` from `package.json` to `manifest.json` before building.
## Conventions
- **No comments** in source code
- **No emojis** in code or UI
- **Indentation:** tabs (Obsidian convention)
- **Imports:** `import type` for type-only imports to avoid circular dependencies
- **File naming:** `kebab-case` for files, `PascalCase` for classes
- **Russian labels** for Russian locale, **English labels** for all other locales
- Module `id` is `kebab-case` and matches the key in the locales `modules` object
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
BUMP="${1:-patch}"
CURRENT_VERSION=$(node -p "require('./manifest.json').version")
if [[ "$BUMP" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
NEW_VERSION="$BUMP"
elif [[ "$BUMP" == "patch" ]]; then
NEW_VERSION=$(node -p "
const [a,b,c] = '$CURRENT_VERSION'.split('.').map(Number);
\`\${a}.\${b}.\${c+1}\`
")
elif [[ "$BUMP" == "minor" ]]; then
NEW_VERSION=$(node -p "
const [a,b] = '$CURRENT_VERSION'.split('.').map(Number);
\`\${a}.\${b+1}.0\`
")
elif [[ "$BUMP" == "major" ]]; then
NEW_VERSION=$(node -p "
const [a] = '$CURRENT_VERSION'.split('.').map(Number);
\`\${a+1}.0.0\`
")
else
echo "Usage: $0 [patch|minor|major|<semver>]"
exit 1
fi
echo "Current: $CURRENT_VERSION"
echo "New: $NEW_VERSION"
read -p "Proceed? [y/N] " CONFIRM
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
echo "Aborted."; exit 1
fi
node -e "
const fs = require('fs');
const m = require('./manifest.json'); m.version = '$NEW_VERSION';
fs.writeFileSync('./manifest.json', JSON.stringify(m, null, '\t') + '\n');
const p = require('./package.json'); p.version = '$NEW_VERSION';
fs.writeFileSync('./package.json', JSON.stringify(p, null, '\t') + '\n');
"
npm run build
git add -f manifest.json package.json main.js styles.css
git commit -m "v$NEW_VERSION"
git tag "v$NEW_VERSION"
echo ""
echo "✔ Release v$NEW_VERSION ready."
echo "Run: git push && git push --tags"
+17
View File
@@ -12,6 +12,14 @@ export interface Locale {
extensionsDesc: string;
extensionsPlaceholder: string;
};
'preserve-link-aliases': {
name: string;
description: string;
processEmbedsLabel: string;
processEmbedsDesc: string;
processCanvasLabel: string;
processCanvasDesc: string;
};
};
}
@@ -32,5 +40,14 @@ export const en: Locale = {
'Comma-separated extensions (e.g.: png, jpg, pdf, mp3). Files with these extensions will be inserted as [[path/file|name]] instead of ![[path/file]].',
extensionsPlaceholder: 'png, jpg, jpeg, gif, svg, webp, pdf',
},
'preserve-link-aliases': {
name: 'Preserve Link Aliases',
description:
'Preserves original link aliases when attachments are renamed',
processEmbedsLabel: 'Process embed links',
processEmbedsDesc: 'Also process ![[embed]] links (disabled by default)',
processCanvasLabel: 'Process Canvas files',
processCanvasDesc: 'Also process links in Canvas (.canvas) files',
},
},
};
+9
View File
@@ -17,5 +17,14 @@ export const ru: Locale = {
'Расширения через запятую (например: png, jpg, pdf, mp3). Файлы с этими расширениями будут вставляться как [[path/file|name]] вместо ![[path/file]].',
extensionsPlaceholder: 'png, jpg, jpeg, gif, svg, webp, pdf',
},
'preserve-link-aliases': {
name: 'Сохранение алиасов ссылок',
description:
'Сохраняет оригинальные алиасы ссылок при переименовании вложений',
processEmbedsLabel: 'Обрабатывать embed-ссылки',
processEmbedsDesc: 'Также обрабатывать ![[embed]] ссылки (по умолчанию выключено)',
processCanvasLabel: 'Обрабатывать Canvas-файлы',
processCanvasDesc: 'Также обрабатывать ссылки в Canvas (.canvas) файлах',
},
},
};
+56 -81
View File
@@ -4,7 +4,6 @@ import {
MarkdownFileInfo,
MarkdownView,
Notice,
PluginSettingTab,
Setting,
normalizePath,
} from 'obsidian';
@@ -33,25 +32,69 @@ export class AttachmentCleanPasteModule extends BaseModule {
onload(plugin: ZeroQoLModulesPlugin, moduleSettings: Record<string, unknown>): void {
this.app = plugin.app;
const settings = moduleSettings as unknown as AttachmentCleanPasteSettings;
plugin.addSettingTab(
new AttachmentCleanPasteSettingTab(plugin.app, plugin, MODULE_ID),
);
const rawSettings = moduleSettings as AttachmentCleanPasteSettings;
const pasteRef = plugin.app.workspace.on(
'editor-paste',
this.createPasteHandler(settings),
this.createPasteHandler(rawSettings),
);
this.registerCleanup(() => plugin.app.workspace.offref(pasteRef));
const dropRef = plugin.app.workspace.on(
'editor-drop',
this.createDropHandler(settings),
this.createDropHandler(rawSettings),
);
this.registerCleanup(() => plugin.app.workspace.offref(dropRef));
}
renderInlineSettings(
containerEl: HTMLElement,
settings: Record<string, unknown>,
saveSettings: () => Promise<void>,
): void {
const loc = locale.modules['attachment-clean-paste'];
const extSettings = settings as unknown as AttachmentCleanPasteSettings;
new Setting(containerEl)
.setName(loc.extensionsLabel)
.setDesc(loc.extensionsDesc)
.addTextArea((textarea) => {
textarea
.setPlaceholder(loc.extensionsPlaceholder)
.setValue(extSettings?.extensions ?? '')
.onChange(async (value) => {
settings.extensions = value;
await saveSettings();
});
textarea.inputEl.rows = 4;
textarea.inputEl.style.width = '100%';
});
}
private async processAll(
files: FileList,
editor: Editor,
settings: AttachmentCleanPasteSettings,
evt: ClipboardEvent | DragEvent,
): Promise<void> {
const matchingFiles = Array.from(files).filter((f) =>
this.shouldProcess(f.name, settings),
);
if (matchingFiles.length === 0) return;
evt.preventDefault();
evt.stopPropagation();
const links: string[] = [];
for (const file of matchingFiles) {
const link = await this.processFile(file, settings);
if (link) links.push(link);
}
if (links.length === 0) return;
editor.replaceSelection(links.join('\n\n'));
}
private createPasteHandler(settings: AttachmentCleanPasteSettings) {
return async (
evt: ClipboardEvent,
@@ -61,17 +104,7 @@ export class AttachmentCleanPasteModule extends BaseModule {
const files = evt.clipboardData?.files;
if (!files || files.length === 0) return;
const matchingFiles = Array.from(files).filter((f) =>
this.shouldProcess(f.name, settings),
);
if (matchingFiles.length === 0) return;
evt.preventDefault();
evt.stopPropagation();
for (const file of matchingFiles) {
await this.processFile(file, editor, settings);
}
await this.processAll(files, editor, settings, evt);
};
}
@@ -84,17 +117,7 @@ export class AttachmentCleanPasteModule extends BaseModule {
const files = evt.dataTransfer?.files;
if (!files || files.length === 0) return;
const matchingFiles = Array.from(files).filter((f) =>
this.shouldProcess(f.name, settings),
);
if (matchingFiles.length === 0) return;
evt.preventDefault();
evt.stopPropagation();
for (const file of matchingFiles) {
await this.processFile(file, editor, settings);
}
await this.processAll(files, editor, settings, evt);
};
}
@@ -119,9 +142,8 @@ export class AttachmentCleanPasteModule extends BaseModule {
private async processFile(
file: File,
editor: Editor,
_settings: AttachmentCleanPasteSettings,
): Promise<void> {
): Promise<string | null> {
try {
const arrayBuffer = await file.arrayBuffer();
const filename = file.name;
@@ -147,58 +169,11 @@ export class AttachmentCleanPasteModule extends BaseModule {
await this.app.vault.createBinary(availablePath, arrayBuffer);
const link = `[[${availablePath}|${nameWithoutExt}]]`;
editor.replaceSelection(link);
return `[[${availablePath}|${nameWithoutExt}]]`;
} catch (e) {
new Notice(`Clean Paste error: ${e.message}`);
console.error('Attachment Clean Paste:', e);
}
}
}
class AttachmentCleanPasteSettingTab extends PluginSettingTab {
private plugin: ZeroQoLModulesPlugin;
private moduleId: string;
constructor(app: App, plugin: ZeroQoLModulesPlugin, moduleId: string) {
super(app, plugin);
this.plugin = plugin;
this.moduleId = moduleId;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
const loc = locale.modules['attachment-clean-paste'];
containerEl.createEl('h2', { text: loc.name });
containerEl.createEl('p', {
text: loc.descriptionShort,
attr: { style: 'color: var(--text-muted); margin-bottom: 20px;' },
});
const rawSettings = this.plugin.settings.modules[this.moduleId]?.settings as
| AttachmentCleanPasteSettings
| undefined;
new Setting(containerEl)
.setName(loc.extensionsLabel)
.setDesc(loc.extensionsDesc)
.addTextArea((textarea) => {
textarea
.setPlaceholder(loc.extensionsPlaceholder)
.setValue(rawSettings?.extensions ?? '')
.onChange(async (value) => {
if (this.plugin.settings.modules[this.moduleId]) {
this.plugin.settings.modules[this.moduleId].settings = {
extensions: value,
};
}
await this.plugin.saveSettings();
});
textarea.inputEl.rows = 4;
textarea.inputEl.style.width = '100%';
});
return null;
}
}
+6
View File
@@ -21,6 +21,12 @@ export abstract class BaseModule implements QoLModule {
this.cleanupFns = [];
}
renderInlineSettings(
_containerEl: HTMLElement,
_settings: Record<string, unknown>,
_saveSettings: () => Promise<void>,
): void {}
protected registerCleanup(fn: () => void): void {
this.cleanupFns.push(fn);
}
+5 -1
View File
@@ -1,4 +1,8 @@
import { AttachmentCleanPasteModule } from './attachment-clean-paste';
import { PreserveLinkAliasesModule } from './preserve-link-aliases';
import { QoLModule } from '../types';
export const MODULES: QoLModule[] = [new AttachmentCleanPasteModule()];
export const MODULES: QoLModule[] = [
new AttachmentCleanPasteModule(),
new PreserveLinkAliasesModule(),
];
+143
View File
@@ -0,0 +1,143 @@
import { Plugin, TFile, Vault, Setting } from 'obsidian';
import type ZeroQoLModulesPlugin from '../../main';
import { BaseModule } from '../base-module';
import { locale } from '../../locales';
const MODULE_ID = 'preserve-link-aliases';
interface PreserveLinkAliasesSettings {
processEmbeds: boolean;
processCanvas: boolean;
}
export class PreserveLinkAliasesModule extends BaseModule {
id = MODULE_ID;
name = locale.modules['preserve-link-aliases'].name;
description = locale.modules['preserve-link-aliases'].description;
private vault: Vault;
get defaultSettings(): Record<string, unknown> {
return {
processEmbeds: false,
processCanvas: false,
};
}
onload(plugin: ZeroQoLModulesPlugin, moduleSettings: Record<string, unknown>): void {
this.vault = plugin.app.vault;
const rawSettings = moduleSettings as PreserveLinkAliasesSettings;
const renameRef = plugin.app.vault.on(
'rename',
(file: TFile, oldPath: string) => this.handleRename(file, oldPath, rawSettings),
);
this.registerCleanup(() => plugin.app.vault.offref(renameRef));
}
renderInlineSettings(
containerEl: HTMLElement,
settings: Record<string, unknown>,
saveSettings: () => Promise<void>,
): void {
const loc = locale.modules['preserve-link-aliases'];
const s = settings as unknown as PreserveLinkAliasesSettings;
new Setting(containerEl)
.setName(loc.processEmbedsLabel)
.setDesc(loc.processEmbedsDesc)
.addToggle((toggle) =>
toggle
.setValue(s.processEmbeds)
.onChange(async (value) => {
s.processEmbeds = value;
await saveSettings();
}),
);
new Setting(containerEl)
.setName(loc.processCanvasLabel)
.setDesc(loc.processCanvasDesc)
.addToggle((toggle) =>
toggle
.setValue(s.processCanvas)
.onChange(async (value) => {
s.processCanvas = value;
await saveSettings();
}),
);
}
private async handleRename(
file: TFile,
oldPath: string,
settings: PreserveLinkAliasesSettings,
): Promise<void> {
const oldName = oldPath.split('/').pop() ?? '';
const newName = file.name;
const oldBasename = oldName.replace(/\.[^/.]+$/, '');
const newBasename = newName.replace(/\.[^/.]+$/, '');
if (oldBasename === newBasename) return;
const files = this.getFilesToProcess(settings);
for (const f of files) {
const content = await this.vault.read(f);
const updated = this.processContent(content, newName, newBasename, oldBasename, settings);
if (updated !== content) {
await this.vault.modify(f, updated);
}
}
}
private getFilesToProcess(settings: PreserveLinkAliasesSettings): TFile[] {
const files: TFile[] = [];
for (const file of this.vault.getMarkdownFiles()) {
files.push(file);
}
if (settings.processCanvas) {
const abstractFiles = this.vault.getFiles();
for (const file of abstractFiles) {
if (file.extension === 'canvas') {
files.push(file as TFile);
}
}
}
return files;
}
private processContent(
content: string,
newFileName: string,
newBasename: string,
oldBasename: string,
settings: PreserveLinkAliasesSettings,
): string {
const wikiLinkRe = /(!?)\[\[([^\]]*?)]]/g;
return content.replace(wikiLinkRe, (fullMatch, prefix, inner) => {
if (prefix === '!' && !settings.processEmbeds) {
return fullMatch;
}
const pipeIndex = inner.indexOf('|');
if (pipeIndex === -1) return fullMatch;
const linkPath = inner.slice(0, pipeIndex);
const alias = inner.slice(pipeIndex + 1);
if (alias !== newBasename) return fullMatch;
const linkFilename = linkPath.split('/').pop();
if (linkFilename !== newFileName) return fullMatch;
return `${prefix}[[${linkPath}|${oldBasename}]]`;
});
}
}
+10 -2
View File
@@ -45,14 +45,14 @@ export class ZeroQSettingTab extends PluginSettingTab {
});
for (const module of MODULES) {
const moduleSettings = this.plugin.settings.modules[module.id];
const moduleCfg = this.plugin.settings.modules[module.id];
new Setting(containerEl)
.setName(module.name)
.setDesc(module.description)
.addToggle((toggle) =>
toggle
.setValue(moduleSettings?.enabled ?? true)
.setValue(moduleCfg?.enabled ?? true)
.onChange(async (value) => {
if (!this.plugin.settings.modules[module.id]) {
this.plugin.settings.modules[module.id] = {
@@ -70,6 +70,14 @@ export class ZeroQSettingTab extends PluginSettingTab {
}
}),
);
if (moduleCfg) {
module.renderInlineSettings(
containerEl,
moduleCfg.settings,
() => this.plugin.saveSettings(),
);
}
}
}
}
+5
View File
@@ -16,4 +16,9 @@ export interface QoLModule {
defaultSettings: Record<string, unknown>;
onload(plugin: Plugin, moduleSettings: Record<string, unknown>): void;
onunload(plugin: Plugin): void;
renderInlineSettings(
containerEl: HTMLElement,
settings: Record<string, unknown>,
saveSettings: () => Promise<void>,
): void;
}