Настройка процесса генерации документации
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
## 1. Формат ведения технической документации агентом
|
||||
|
||||
## 1.1. Общие принципы
|
||||
|
||||
Техническая документация, формируемая агентом, должна строиться как система атомарных, не пересекающихся по смыслу документов, связанных между собой явными ссылками.
|
||||
|
||||
Ключевые принципы:
|
||||
- один документ описывает одну сущность или один устойчивый технический аспект;
|
||||
- документ не должен дублировать соседние документы;
|
||||
- общая система знаний должна собираться через ссылки, а не через копипасту;
|
||||
- структура документации должна быть пригодна как для чтения человеком, так и для индексирования в RAG.
|
||||
|
||||
## 1.2. Требования к заголовкам
|
||||
|
||||
- Заголовок должен отражать только суть раздела.
|
||||
- Заголовок не должен содержать метаданные (`id`, `doc_type`, `application`, `platform`, `domain`, `sub_domain`).
|
||||
- Метаданные указываются отдельными строками в теле раздела или в YAML frontmatter.
|
||||
|
||||
Пример:
|
||||
- правильно: `## 6.2 Метод UFS получения списка заказов`
|
||||
- неправильно: `## 6.2 Блок api_method (id=..., platform=ufs)`
|
||||
|
||||
## 1.3. Базовые типы документных единиц
|
||||
|
||||
Базовые типы:
|
||||
- `ui_page`
|
||||
- `api_method`
|
||||
- `logic_block`
|
||||
|
||||
Дополнительно могут использоваться:
|
||||
- `architecture_overview`
|
||||
- `integration_doc`
|
||||
- `domain_entity`
|
||||
- `glossary_item`
|
||||
- `index_page`
|
||||
|
||||
## 1.4. Принцип декомпозиции страниц / файлов
|
||||
|
||||
### Один устойчивый объект - один документ
|
||||
Если объект можно переиспользовать или на него могут ссылаться другие документы, его нужно выносить в отдельный файл.
|
||||
|
||||
### Документы не должны пересекаться по смыслу
|
||||
Если описание повторяется в нескольких местах, нужно выделять общий документ и ссылаться на него.
|
||||
|
||||
### Use case и детали живут раздельно
|
||||
Сценарий описывает поток работы, а детали выносятся в функциональные требования, отдельные блоки логики или контрактные описания.
|
||||
|
||||
## 1.5. Иерархическая организация документации
|
||||
|
||||
Документация должна быть организована как иерархическое дерево каталогов и файлов.
|
||||
|
||||
Пример:
|
||||
|
||||
```text
|
||||
docs/
|
||||
ui/
|
||||
api/
|
||||
logic/
|
||||
domains/
|
||||
integrations/
|
||||
architecture/
|
||||
glossary/
|
||||
errors/
|
||||
```
|
||||
|
||||
## 1.6. Учет связей между документами
|
||||
|
||||
Связи должны быть явными.
|
||||
|
||||
Примеры:
|
||||
- UI-страница ссылается на вызываемые API;
|
||||
- API-документ ссылается на используемые блоки логики;
|
||||
- логический блок ссылается на интеграции;
|
||||
- документ по коду ссылается на системную аналитику, инициировавшую изменения.
|
||||
|
||||
## 1.7. Формат markdown-документов
|
||||
|
||||
Каждый документ состоит из:
|
||||
1. YAML frontmatter;
|
||||
2. Markdown body.
|
||||
|
||||
## 1.8. YAML frontmatter
|
||||
|
||||
### Обязательные поля
|
||||
- `id`
|
||||
- `title`
|
||||
- `doc_type`
|
||||
- `status`
|
||||
- `domain`
|
||||
- `sub_domain`
|
||||
- `related_docs`
|
||||
|
||||
### Рекомендуемые поля
|
||||
- `owner`
|
||||
- `entities`
|
||||
- `tags`
|
||||
- `feature`
|
||||
- `system_analytics_refs`
|
||||
- `source_of_truth`
|
||||
- `related_code`
|
||||
|
||||
### Допустимые значения `doc_type`
|
||||
- `ui_page`
|
||||
- `api_method`
|
||||
- `logic_block`
|
||||
- `architecture_overview`
|
||||
- `integration_doc`
|
||||
- `domain_entity`
|
||||
- `glossary_item`
|
||||
- `index_page`
|
||||
|
||||
### Допустимые значения `status`
|
||||
- `draft`
|
||||
- `in_review`
|
||||
- `approved`
|
||||
- `outdated`
|
||||
- `generated`
|
||||
- `active`
|
||||
|
||||
## 1.9. Синхронизация с системной аналитикой
|
||||
|
||||
Техническая документация строится на основе системной аналитики (features).
|
||||
|
||||
Обязательно учитывать:
|
||||
- концептуальный уровень аналитики;
|
||||
- детализацию технической документации;
|
||||
- согласованность терминов, ролей и интеграционных цепочек.
|
||||
|
||||
Если атрибуты или детали отсутствуют в аналитике:
|
||||
- определить их из текста аналитики;
|
||||
- дополнить данными из репозитория (код, контракты, существующие документы);
|
||||
- зафиксировать итог в документации как явные метаданные и требования.
|
||||
|
||||
## 1.10. Формат body-разделов для блока изменений
|
||||
|
||||
Для секции изменений (по аналогии с разделом `6` в аналитике) использовать единый формат.
|
||||
|
||||
Под корнем секции изменений указывать общие атрибуты:
|
||||
- `domain`
|
||||
- `sub_domain`
|
||||
|
||||
Для каждого подраздела `X.Y` указывать метаданные строками сразу после заголовка:
|
||||
- `id`
|
||||
- `doc_type`
|
||||
- `application`
|
||||
- `platform`
|
||||
|
||||
## 1.11. Различие аналитики и документации
|
||||
|
||||
- Аналитика - концептуальный уровень, упрощенный use case.
|
||||
- Документация - детальный инженерный уровень.
|
||||
|
||||
Для документации:
|
||||
- технический use case должен быть детализированным;
|
||||
- функциональные требования расширяют use case и описывают детали интеграций, логики и поведения;
|
||||
- функциональные требования не должны копировать шаги сценария без добавления новой информации.
|
||||
|
||||
Источник правил:
|
||||
- `src/app/core/agent/processes/v2/doc_rules_v2/common-elements/tech-use-case.md`
|
||||
- `src/app/core/agent/processes/v2/doc_rules_v2/common-elements/fr.md`
|
||||
|
||||
## 1.12. Требования к `ui_page`
|
||||
|
||||
Обязательная структура:
|
||||
- `### Технический use case`
|
||||
- `### Требования к UI`
|
||||
- `### Функциональные требования`
|
||||
- `### Нефункциональные требования`
|
||||
|
||||
### Требования к UI
|
||||
Внутри обязательно отдельно описывать каждую форму UI:
|
||||
- табличное представление;
|
||||
- пустой список (empty state);
|
||||
- ошибка (error state).
|
||||
|
||||
Обязательные правила:
|
||||
- если есть интеграция, обязательно описать показ ошибки;
|
||||
- если показывается список, обязательно описать показ отсутствия данных.
|
||||
|
||||
### UI-элементы
|
||||
UI-поля и элементы в документации описываются строго в таблицах.
|
||||
|
||||
Обязательные колонки (заполнять там, где применимо):
|
||||
- `Код элемента`
|
||||
- `Название и описание`
|
||||
- `Данные`
|
||||
- `Поведение`
|
||||
- `Валидация`
|
||||
|
||||
## 1.13. Пользовательская аналитика для `ui_page`
|
||||
|
||||
События пользовательской аналитики оформляются таблицей:
|
||||
- `Название события`
|
||||
- `Описание`
|
||||
- `Точка вызова`
|
||||
- `Payload`
|
||||
|
||||
## 1.14. Требования к `api_method`
|
||||
|
||||
Обязательная структура:
|
||||
- `### Технический use case`
|
||||
- `### Функциональные требования`
|
||||
- `### Нефункциональные требования`
|
||||
- `### Контракт`
|
||||
|
||||
### Технический use case
|
||||
Оформляется детально по правилам `tech-use-case.md`.
|
||||
|
||||
Обязательные части:
|
||||
- название
|
||||
- предусловия
|
||||
- триггер
|
||||
- основной сценарий
|
||||
- альтернативный сценарий
|
||||
- обработка ошибок
|
||||
- постусловие
|
||||
|
||||
### Функциональные требования
|
||||
Оформляются по правилам `fr.md`:
|
||||
- формат `FR.<номер>. <Название>`;
|
||||
- FR расширяют use case;
|
||||
- FR не дублируют шаги сценария без дополнительной ценности;
|
||||
- для интеграционных шагов FR обязательны.
|
||||
|
||||
## 1.15. Нефункциональные требования для `api_method`
|
||||
|
||||
Разделять на подразделы:
|
||||
- `#### Аудит` (если применимо)
|
||||
- `#### Мониторинг`
|
||||
|
||||
### Мониторинг
|
||||
Оформлять таблицей:
|
||||
- `Метрика`
|
||||
- `Описание`
|
||||
- `Условие срабатывания`
|
||||
|
||||
Правила:
|
||||
- в условиях указывать, при каких состояниях фиксируется событие;
|
||||
- не использовать формулировку вида «точка измерения = метод»;
|
||||
- базово закладывать метрики:
|
||||
- `<METRIC_NAME>_SUCCESS`
|
||||
- `<METRIC_NAME>_FAIL`
|
||||
- `<METRIC_NAME>_BUSINESS_ERROR`
|
||||
|
||||
## 1.16. Распределение ответственности по слоям
|
||||
|
||||
- Проверка ролевой модели пользователя обычно выполняется в `ufs`.
|
||||
- Для `pprb` аудит может не фиксироваться, если это согласовано правилами домена.
|
||||
- Если проверка ролей вынесена в `ufs`, не дублировать этот шаг в use case `pprb`.
|
||||
|
||||
## 1.17. Контракты API
|
||||
|
||||
Контракт может быть:
|
||||
- в markdown-таблицах;
|
||||
- в OpenAPI;
|
||||
- в отдельном контрактном файле.
|
||||
|
||||
Для markdown-контракта минимум:
|
||||
- endpoint/method;
|
||||
- request fields;
|
||||
- required/optional;
|
||||
- constraints;
|
||||
- response;
|
||||
- errors;
|
||||
- auth;
|
||||
- retry;
|
||||
- timeout;
|
||||
- idempotency.
|
||||
|
||||
## 1.18. Integrations-блок
|
||||
|
||||
Если у документа есть интеграции, выделять отдельный `## Integrations`.
|
||||
|
||||
Рекомендуемые атрибуты интеграции:
|
||||
- `target`
|
||||
- `target_type`
|
||||
- `direction`
|
||||
- `interaction`
|
||||
- `via`
|
||||
- `purpose`
|
||||
- `details`
|
||||
|
||||
## 1.19. Общие требования к markdown body
|
||||
|
||||
- В документе должен быть один `H1`, совпадающий с `title`.
|
||||
- Основные разделы - `H2`, подразделы - `H3`.
|
||||
- Не допускать хаотичной вложенности заголовков.
|
||||
- Вместо дублирования использовать ссылки на связанные документы.
|
||||
- Сценарии, правила, ограничения и кодовые привязки держать раздельно.
|
||||
@@ -0,0 +1,185 @@
|
||||
# Системная аналитика
|
||||
|
||||
## Общее описание
|
||||
|
||||
Документ описывает изменения в автоматизированной системе. Пишется системными аналитиками для разработчиков и тестировщиков и проходит согласование с экспертами по архитектуре, безопасности и сопровождению.
|
||||
|
||||
Документ может описывать как новый процесс, так и инкремент доработки существующей функциональности.
|
||||
|
||||
## Требования к заголовкам
|
||||
|
||||
- Заголовок должен отражать суть раздела.
|
||||
- Заголовок не должен содержать лишнюю информацию, которая относится к метаданным (id, doc_type, platform, application и т.д.).
|
||||
- Метаданные указываются отдельными строками в теле раздела.
|
||||
|
||||
## Состав документа
|
||||
|
||||
Каждый раздел верхнего уровня оформляется заголовком уровня `#`.
|
||||
|
||||
### 1. Цели
|
||||
|
||||
- Коротко описать, какую проблему и для кого решаем.
|
||||
- 1-2 предложения.
|
||||
- Не дублировать критерии приемки.
|
||||
|
||||
### 2. Процесс AS IS и TO BE
|
||||
|
||||
- Фокус на пользовательских и бизнес-изменениях.
|
||||
- Не указывать технические детали (платформы, API, внутренние интеграции).
|
||||
|
||||
### 3. Ограничения
|
||||
|
||||
- Ограничения и допущения в техническом и бизнесовом плане.
|
||||
|
||||
### 4. Критерии приемки
|
||||
|
||||
- Описывать с точки зрения пользователя.
|
||||
- Не добавлять технические детали (платформы, API, внутренние компоненты).
|
||||
|
||||
### 5. Архитектура
|
||||
|
||||
Нужно указать:
|
||||
|
||||
- схему контейнеров,
|
||||
- таблицу интеграций,
|
||||
- сквозные интеграционные сценарии.
|
||||
|
||||
Слои:
|
||||
|
||||
- `ui` - web-приложение, клиент.
|
||||
- `ufs` - BFF: аутентификация/авторизация, агрегация и маппинг данных.
|
||||
- `pprb` - backend: API, БД, логика жизненного цикла сущностей.
|
||||
|
||||
#### Диаграмма
|
||||
|
||||
Mermaid-диаграмма должна содержать:
|
||||
|
||||
- основные контейнеры,
|
||||
- названия приложений и платформ,
|
||||
- интеграции между приложениями,
|
||||
- названия вызываемых endpoint или топиков.
|
||||
|
||||
#### Таблица интеграций
|
||||
|
||||
Обязательные колонки:
|
||||
|
||||
- Код
|
||||
- Название endpoint/топика
|
||||
- Источник данных
|
||||
- Потребитель данных
|
||||
- Инициатор вызова
|
||||
- Передаваемые данные
|
||||
|
||||
#### Сквозной интеграционный сценарий
|
||||
|
||||
- Нумерованный список вызовов вида: «Компонент 1 вызывает endpoint в Компонент 2».
|
||||
- Только интеграционная цепочка, без детального разбора логики.
|
||||
|
||||
### 6. Описание изменений
|
||||
|
||||
Раздел состоит из подразделов уровня `##` (например, `6.1`, `6.2`, `6.3`).
|
||||
|
||||
Под корнем раздела `# 6` указываются общие метаданные:
|
||||
|
||||
- `domain`
|
||||
- `sub_domain`
|
||||
|
||||
Для каждого раздела `6.x` обязательно указывать метаданные строками сразу после заголовка:
|
||||
|
||||
- `id`
|
||||
- `doc_type`
|
||||
- `application`
|
||||
- `platform`
|
||||
|
||||
#### 6.x для `ui_page`
|
||||
|
||||
Обязательная структура:
|
||||
|
||||
- `### Технический use case (тезисно)`
|
||||
- `### Требования к UI`
|
||||
- `### Функциональные требования`
|
||||
- `### Нефункциональные требования`
|
||||
|
||||
Требования к разделу `### Требования к UI`:
|
||||
|
||||
- Внутри нужно отдельно описывать каждую UI-форму.
|
||||
- Если есть интеграция, обязательно описать, как показывается ошибка.
|
||||
- Если показываем список, обязательно описать, как показывается отсутствие данных.
|
||||
|
||||
Рекомендуемая детализация UI-форм:
|
||||
|
||||
- табличное представление,
|
||||
- пустой список (empty state),
|
||||
- ошибка (error state).
|
||||
|
||||
Правила описания UI-полей:
|
||||
|
||||
- Поля описывать списком (не таблицей).
|
||||
- Общие правила (например, read-only, поведение при пустом значении) выносить в общий блок, не дублировать для каждого поля.
|
||||
|
||||
Нефункциональные требования для `ui_page`:
|
||||
|
||||
- пользовательская аналитика оформляется таблицей с колонками:
|
||||
- `Название события`
|
||||
- `Описание`
|
||||
- `Точка вызова`
|
||||
- `Payload`
|
||||
|
||||
#### 6.x для `api_method`
|
||||
|
||||
Обязательная структура:
|
||||
|
||||
- `### Технический use case (тезисно)`
|
||||
- `### Функциональные требования`
|
||||
- `### Нефункциональные требования`
|
||||
- `### Контракт метода`
|
||||
|
||||
Правило для функциональных требований:
|
||||
|
||||
- Если дополнительных требований нет (дублируют сценарий), писать: `Не выявлены`.
|
||||
|
||||
Нефункциональные требования:
|
||||
|
||||
- Разделять на подразделы:
|
||||
- `#### Аудит` (если применимо)
|
||||
- `#### Мониторинг`
|
||||
|
||||
Для `Мониторинг` использовать таблицу с колонками:
|
||||
|
||||
- `Метрика`
|
||||
- `Описание`
|
||||
- `Условие срабатывания`
|
||||
|
||||
Важно:
|
||||
|
||||
- В мониторинге описывать условия срабатывания, а не «точку измерения = метод».
|
||||
- Базово закладывать 3 метрики:
|
||||
- `<METRIC_NAME>_SUCCESS`
|
||||
- `<METRIC_NAME>_FAIL`
|
||||
- `<METRIC_NAME>_BUSINESS_ERROR`
|
||||
|
||||
Контракт метода:
|
||||
|
||||
- Для запроса: таблица параметров (`header/query/path`) с колонками: название, тип параметра, тип данных, обязательность, описание, пример.
|
||||
- Для тела JSON (если есть): структура отдельной таблицей.
|
||||
- Для ответа JSON: таблица с колонками: название, тип данных, обязательность, описание, заполнение, пример.
|
||||
|
||||
#### 6.x для `logic_block`
|
||||
|
||||
Обязательная структура:
|
||||
|
||||
- `### Технический use case (тезисно)`
|
||||
- `### Функциональные требования`
|
||||
- `### Нефункциональные требования`
|
||||
|
||||
## Дополнительные правила по слоям
|
||||
|
||||
- Проверка ролевой модели пользователя обычно выполняется на уровне `ufs`.
|
||||
- Для `pprb` аудит может не фиксироваться, если это правило принято для конкретной фичи/домена.
|
||||
- Если проверка ролей вынесена в `ufs`, не дублировать этот шаг в сценарии `pprb`.
|
||||
|
||||
## Термины
|
||||
|
||||
- Аудит: события, которые фиксируют действия пользователя и позволяют ответить на вопрос «кто, что, когда сделал».
|
||||
- Мониторинг: технические события/метрики для контроля стабильности и поиска сбоев.
|
||||
|
||||
@@ -1,852 +0,0 @@
|
||||
## 1. Формат ведения технической документации агентом
|
||||
|
||||
## 1.1. Общие принципы
|
||||
|
||||
Техническая документация, формируемая агентом, должна строиться как **система атомарных, не пересекающихся по смыслу документов**, связанных между собой явными ссылками.
|
||||
|
||||
Ключевые принципы:
|
||||
- один документ описывает одну сущность или один устойчивый технический аспект;
|
||||
- документ не должен дублировать соседние документы;
|
||||
- общая система знаний должна собираться через ссылки, а не через копипасту;
|
||||
- структура документации должна быть пригодна как для чтения человеком, так и для индексирования в RAG.
|
||||
|
||||
## 1.2. Базовые типы документных единиц
|
||||
|
||||
На первом этапе логично сохранить текущую семантику типов документов, но перенести ее в файловую модель.
|
||||
|
||||
Базовые типы:
|
||||
- `ui_page`
|
||||
- `api_method`
|
||||
- `logic_block`
|
||||
|
||||
Позже могут добавиться:
|
||||
- `architecture_overview`
|
||||
- `integration_doc`
|
||||
- `domain_entity`
|
||||
- `glossary_item`
|
||||
- `index_page`
|
||||
|
||||
## 1.3. Принцип декомпозиции страниц / файлов
|
||||
|
||||
### Один устойчивый объект — один документ
|
||||
Если объект можно переиспользовать или на него могут ссылаться другие документы, его надо выносить в отдельный файл.
|
||||
|
||||
Примеры:
|
||||
- отдельная UI-страница;
|
||||
- отдельный API endpoint;
|
||||
- отдельный блок логики;
|
||||
- отдельный интеграционный сценарий.
|
||||
|
||||
### Документы не должны пересекаться по смыслу
|
||||
Если описание повторяется в нескольких местах, нужно выделять общий документ и ссылаться на него.
|
||||
|
||||
Примеры:
|
||||
- фронтальная страница не должна заново описывать логику API;
|
||||
- документ по API не должен заново раскрывать общую логику переиспользуемого блока;
|
||||
- вместо дублирования должен быть переход по ссылке.
|
||||
|
||||
### Use case и детальные правила живут раздельно
|
||||
Сценарий описывает поток работы, а детали выносятся в функциональные требования, отдельные блоки логики или контрактные описания.
|
||||
|
||||
Это важно и для RAG-индексации:
|
||||
- сценарии индексируются как workflows;
|
||||
- отдельные правила — как facts;
|
||||
- сущности и блоки — как entities.
|
||||
|
||||
## 1.4. Иерархическая организация документации
|
||||
|
||||
Документация должна быть организована как иерархическое дерево каталогов и файлов, а не как набор неструктурированных страниц.
|
||||
|
||||
Пример верхнего уровня:
|
||||
|
||||
```text
|
||||
docs/
|
||||
ui/
|
||||
api/
|
||||
logic/
|
||||
domains/
|
||||
integrations/
|
||||
architecture/
|
||||
glossary/
|
||||
errors/
|
||||
```
|
||||
|
||||
Пример организации:
|
||||
|
||||
```text
|
||||
docs/
|
||||
ui/
|
||||
order-create-page.md
|
||||
order-edit-page.md
|
||||
api/
|
||||
orders-create.md
|
||||
orders-get.md
|
||||
logic/
|
||||
order-validation.md
|
||||
order-enrichment.md
|
||||
architecture/
|
||||
system-overview.md
|
||||
integration-landscape.md
|
||||
errors/
|
||||
catalog.yaml
|
||||
```
|
||||
|
||||
## 1.5. Учет связей между документами
|
||||
|
||||
Связи должны быть **явными и поддерживаемыми агентом**.
|
||||
|
||||
Примеры:
|
||||
- UI-страница ссылается на API, который она вызывает;
|
||||
- API-документ ссылается на переиспользуемые логические блоки;
|
||||
- логический блок ссылается на связанные интеграции;
|
||||
- архитектурный обзор ссылается на набор конкретных модулей и документов;
|
||||
- документ по коду может ссылаться на системную аналитику, которая инициировала изменение.
|
||||
|
||||
Именно эта сеть ссылок затем индексируется в слоях:
|
||||
- `D1_DOCUMENT_CATALOG`
|
||||
- `D3_ENTITY_CATALOG`
|
||||
- `D4_WORKFLOW_INDEX`
|
||||
- `D5_REFERENCE_GRAPH`
|
||||
- `D6_DOC_CODE_LINKS`
|
||||
|
||||
## 1.6. Формат markdown-документов
|
||||
|
||||
Основной формат технической документации — `Markdown`.
|
||||
|
||||
Каждый документ состоит из двух частей:
|
||||
1. **YAML frontmatter** — структурные метаданные;
|
||||
2. **Markdown body** — основное содержимое по шаблону.
|
||||
|
||||
## 3.7. YAML frontmatter
|
||||
|
||||
Frontmatter нужен для:
|
||||
- определения типа документа;
|
||||
- идентификации документа;
|
||||
- определения его места в иерархии;
|
||||
- фиксации связей с кодом и другими документами;
|
||||
- выделения сущностей и тегов;
|
||||
- упрощения построения слоев `D1`, `D3`, `D5`, `D6`.
|
||||
|
||||
### Базовый frontmatter
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: ui-order-create-page
|
||||
title: Страница создания заказа
|
||||
doc_type: ui_page
|
||||
domain: orders
|
||||
status: draft
|
||||
related_docs:
|
||||
- api-orders-create
|
||||
- logic-order-validation
|
||||
entities:
|
||||
- Order
|
||||
- CreateOrder
|
||||
tags:
|
||||
- ui
|
||||
- orders
|
||||
- creation
|
||||
#owner: system-analyst
|
||||
#source_of_truth: code
|
||||
#related_code:
|
||||
# - src/orders/ui/create_page.tsx
|
||||
# - src/orders/api/orders_controller.py
|
||||
---
|
||||
```
|
||||
|
||||
### Обязательные поля
|
||||
|
||||
- `id` — стабильный уникальный идентификатор документа;
|
||||
- `title` — человекочитаемый заголовок;
|
||||
- `doc_type` — тип документа;
|
||||
- `related_docs` — ссылки на связанные документы;
|
||||
- `status` — статус документа;
|
||||
- `domain` - домен фичи (Карточка клиента, Задачи, Сделки, Предложения)
|
||||
- `sub_domain` - поддомен внутри основной сущности (Счета, ЗДА, ECM)
|
||||
|
||||
### Рекомендуемые поля
|
||||
- `owner`
|
||||
- `entities`
|
||||
- `tags`
|
||||
- `parent`
|
||||
- `children`
|
||||
- `feature`
|
||||
- `system_analytics_refs`
|
||||
- `business_refs`
|
||||
- `updated_from`
|
||||
- `reviewers`
|
||||
- `source_of_truth`
|
||||
- `related_code`
|
||||
|
||||
### Допустимые значения `doc_type`
|
||||
- `ui_page`
|
||||
- `api_method`
|
||||
- `logic_block`
|
||||
- `architecture_overview`
|
||||
- `integration_doc`
|
||||
- `domain_entity`
|
||||
- `glossary_item`
|
||||
- `index_page`
|
||||
|
||||
### Допустимые значения `status`
|
||||
- `draft`
|
||||
- `in_review`
|
||||
- `approved`
|
||||
- `outdated`
|
||||
- `generated`
|
||||
- `active`
|
||||
|
||||
### Допустимые значения `source_of_truth`
|
||||
- `code`
|
||||
- `doc`
|
||||
- `system_analysis`
|
||||
- `business_requirements`
|
||||
- `mixed`
|
||||
|
||||
## 1.8. Typed frontmatter для разных типов документов
|
||||
|
||||
У каждого типа документа есть:
|
||||
- **общие поля**;
|
||||
- **тип-специфичные поля**.
|
||||
|
||||
### Пример для `api_method`
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: api.create_invoice
|
||||
doc_type: api_method
|
||||
domain: billing
|
||||
title: Создание инвойса
|
||||
|
||||
endpoint: POST /api/v1/invoices
|
||||
auth: USER
|
||||
idempotent: false
|
||||
timeout_ms: 3000
|
||||
|
||||
links:
|
||||
called_by:
|
||||
- ui.invoice_form
|
||||
uses_logic:
|
||||
- logic.invoice_validation
|
||||
writes_db:
|
||||
- db.invoices
|
||||
- db.invoice_items
|
||||
integrates_with:
|
||||
- int.crm_sync
|
||||
|
||||
related_docs:
|
||||
- ui.invoice_form
|
||||
- logic.invoice_validation
|
||||
related_code:
|
||||
- services/billing/api/create_invoice.py
|
||||
entities:
|
||||
- Invoice
|
||||
- CreateInvoice
|
||||
|
||||
tags:
|
||||
- invoice
|
||||
- create
|
||||
- billing
|
||||
status: active
|
||||
version: 1.3
|
||||
source_of_truth: code
|
||||
---
|
||||
```
|
||||
|
||||
### Для `api_method` полезно поддерживать
|
||||
|
||||
- `endpoint`
|
||||
- `sup_parameters`
|
||||
- `role_model_actions`
|
||||
- `monitoring_actions`
|
||||
- `audit_actions`
|
||||
- `idempotent`
|
||||
- `timeout_ms`
|
||||
- `links.called_by`
|
||||
- `links.uses_logic`
|
||||
- `links.writes_db`
|
||||
- `links.integrates_with`
|
||||
|
||||
### Для `ui_page` позже полезно поддерживать
|
||||
- `calls_api`
|
||||
- `user_analitycs_actions`
|
||||
- `sup_parameters`
|
||||
- `role_model_actions`
|
||||
- `entry_points`
|
||||
- `uses_logic`
|
||||
|
||||
### Для `logic_block` полезно поддерживать
|
||||
|
||||
- `called_from`
|
||||
- `uses_logic`
|
||||
- `reads_db`
|
||||
- `writes_db`
|
||||
- `integrates_with`
|
||||
|
||||
## 1.9. Двухслойная структура документа: `Summary` + `Details`
|
||||
|
||||
LLM не должна каждый раз тонуть в полном документе. Поэтому каждый документ должен содержать два уровня представления.
|
||||
|
||||
### `Summary`
|
||||
Короткая, строго структурированная спецификация. Это слой **быстрого контекста**.
|
||||
|
||||
Рекомендуемый объем:
|
||||
- примерно 30–60 строк;
|
||||
- без длинных пояснений;
|
||||
- только ключевые факты.
|
||||
|
||||
Пример:
|
||||
|
||||
```md
|
||||
## Summary
|
||||
- Purpose: создание инвойса из формы
|
||||
- Actor: пользователь
|
||||
- Trigger: Submit
|
||||
- Main API: POST /api/v1/invoices (api.create_invoice)
|
||||
- Validation: required fields, amount > 0, date <= today
|
||||
- Errors: 400(field errors), 409(duplicate), 503(retryable)
|
||||
- Analytics: event invoice_submit, invoice_error
|
||||
```
|
||||
|
||||
### `Details`
|
||||
Полное раскрытие объекта:
|
||||
- use case;
|
||||
- функциональные требования;
|
||||
- UI;
|
||||
- API;
|
||||
- integrations;
|
||||
- ошибки;
|
||||
- НФТ;
|
||||
- связи;
|
||||
- кодовые привязки.
|
||||
|
||||
### Блок `## Integrations`
|
||||
|
||||
Если у объекта есть интеграции, они должны быть выделены в отдельный блок `## Integrations`.
|
||||
Интеграции не нужно дублировать во frontmatter.
|
||||
Основное описание хранится в body документа.
|
||||
|
||||
Ожидаемый принцип:
|
||||
- одна интеграция = одна отдельная запись внутри блока;
|
||||
- у интеграции есть краткое имя;
|
||||
- у интеграции есть структурированные атрибуты;
|
||||
- дополнительные детали допускаются в свободной форме через вложенный словарь.
|
||||
|
||||
Рекомендуемые атрибуты интеграции:
|
||||
- `target`
|
||||
- `target_type`
|
||||
- `direction`
|
||||
- `interaction`
|
||||
- `via`
|
||||
- `purpose`
|
||||
- `details`
|
||||
|
||||
Где:
|
||||
- `target` - идентификатор или имя целевого объекта;
|
||||
- `target_type` - тип цели: `api`, `ui`, `entity`, `service`, `queue`, `db`, `external_system`;
|
||||
- `direction` - направление: `inbound`, `outbound`, `bidirectional`;
|
||||
- `interaction` - тип взаимодействия: `calls`, `reads`, `writes`, `emits`, `consumes`, `depends_on`;
|
||||
- `via` - технический канал интеграции;
|
||||
- `purpose` - зачем нужна интеграция;
|
||||
- `details` - словарь с гибкой структурой под дополнительные параметры.
|
||||
|
||||
Пример:
|
||||
|
||||
```md
|
||||
## Integrations
|
||||
|
||||
### Orders API
|
||||
- target: api.orders.create
|
||||
- target_type: api
|
||||
- direction: outbound
|
||||
- interaction: calls
|
||||
- via: POST /api/orders
|
||||
- purpose: создание заказа
|
||||
- details:
|
||||
- auth: service-token
|
||||
- retry: true
|
||||
|
||||
### Order Entity
|
||||
- target: domain.order
|
||||
- target_type: entity
|
||||
- direction: outbound
|
||||
- interaction: writes
|
||||
- via: repository
|
||||
- purpose: сохранение состояния заказа
|
||||
- details:
|
||||
- transaction: required
|
||||
```
|
||||
|
||||
Этот блок должен быть пригоден и для чтения человеком, и для последующего извлечения в отдельный RAG-слой интеграций.
|
||||
|
||||
## 1.10. Общие требования к markdown body
|
||||
|
||||
1. В документе должен быть один `H1`, совпадающий с `title`.
|
||||
2. Основные разделы используют `H2`.
|
||||
3. Подразделы внутри разделов используют `H3`.
|
||||
4. Не должно быть хаотической вложенности заголовков.
|
||||
5. Один раздел должен описывать одну смысловую часть.
|
||||
6. Текст не должен дублировать соседние документы.
|
||||
7. Вместо дублирования должны использоваться явные ссылки на связанные документы.
|
||||
8. Сценарии, правила, ограничения и ссылки на код должны быть отделены друг от друга.
|
||||
|
||||
## 1.11. Базовый каркас markdown-документа
|
||||
|
||||
```md
|
||||
---
|
||||
id: api-orders-create
|
||||
title: Метод создания заказа
|
||||
doc_type: api_method
|
||||
domain: orders
|
||||
status: draft
|
||||
source_of_truth: code
|
||||
related_docs:
|
||||
- logic-order-validation
|
||||
- ui-order-create-page
|
||||
related_code:
|
||||
- src/orders/api/create_order.py
|
||||
entities:
|
||||
- Order
|
||||
- CreateOrder
|
||||
tags:
|
||||
- api
|
||||
- orders
|
||||
---
|
||||
|
||||
# Метод создания заказа
|
||||
|
||||
## Summary
|
||||
- Purpose: создание заказа
|
||||
- Actor: пользователь
|
||||
- Trigger: submit формы
|
||||
- Main API: POST /orders
|
||||
|
||||
## Details
|
||||
### Описание
|
||||
### Технический use case
|
||||
### Функциональные требования
|
||||
### Нефункциональные требования
|
||||
### Контракт
|
||||
|
||||
|
||||
## 3.13. Специализированные шаблоны документов
|
||||
|
||||
### UI Page
|
||||
|
||||
```md
|
||||
# <Название страницы>
|
||||
|
||||
## Summary
|
||||
## Назначение
|
||||
## Контекст
|
||||
## Технический use case
|
||||
## Описание UI
|
||||
## UI Elements
|
||||
## Функциональные требования
|
||||
## Нефункциональные требования
|
||||
## Связанные API
|
||||
## Связанные блоки логики
|
||||
## Связанный код
|
||||
## Связанные документы
|
||||
## История изменений
|
||||
```
|
||||
|
||||
#### Требования к разделу `Описание UI`
|
||||
Для каждого элемента желательно описывать:
|
||||
- тип элемента;
|
||||
- назначение;
|
||||
- источник данных;
|
||||
- default / placeholder;
|
||||
- правила активации;
|
||||
- поведение при взаимодействии;
|
||||
- валидацию.
|
||||
|
||||
#### Требования к разделу `UI Elements`
|
||||
UI-элементы должны храниться в **табличном** или **полуструктурированном** виде.
|
||||
|
||||
Пример:
|
||||
|
||||
```md
|
||||
## UI Elements
|
||||
|
||||
| id | type | label | data_source | validation | behavior |
|
||||
|--------|--------|---------|------------|------------|----------|
|
||||
| amount | input | Amount | local | >0 | enables submit |
|
||||
| submit | button | Create | - | - | calls api.create_invoice |
|
||||
```
|
||||
|
||||
Если модель UI сложная, допустим sidecar-файл `ui_elements.yaml` или `ui_elements.json` рядом с основным документом.
|
||||
|
||||
### API Method
|
||||
|
||||
```md
|
||||
# <Название API метода>
|
||||
|
||||
## Summary
|
||||
## Назначение
|
||||
## Контекст
|
||||
## Технический use case
|
||||
## Функциональные требования
|
||||
## Contract
|
||||
## Integrations
|
||||
## Errors
|
||||
## Нефункциональные требования
|
||||
## Связанные блоки логики
|
||||
## Связанный код
|
||||
## Связанные документы
|
||||
## История изменений
|
||||
```
|
||||
|
||||
#### Требования к разделу `Contract`
|
||||
Контракт может:
|
||||
- быть кратко описан прямо в документе;
|
||||
- ссылаться на OpenAPI;
|
||||
- ссылаться на отдельный контрактный файл.
|
||||
|
||||
Для REST API целевым источником истины должен становиться `OpenAPI`.
|
||||
|
||||
### Reusable Logic Block
|
||||
|
||||
```md
|
||||
# <Название блока логики>
|
||||
|
||||
## Summary
|
||||
## Назначение
|
||||
## Контекст
|
||||
## Технический use case
|
||||
## Функциональные требования
|
||||
## Integrations
|
||||
## Ограничения и условия вызова
|
||||
## Нефункциональные требования
|
||||
## Связанные API / UI / integration points
|
||||
## Связанный код
|
||||
## Связанные документы
|
||||
## История изменений
|
||||
```
|
||||
|
||||
## 3.14. Машинно-читаемые API-контракты
|
||||
|
||||
Для API контрактов **источником истины** должен становиться:
|
||||
- `OpenAPI` — предпочтительно;
|
||||
- либо временно строгий markdown/yaml-контракт, если OpenAPI еще нет.
|
||||
|
||||
Минимальный набор для API-контракта:
|
||||
- `endpoint`
|
||||
- `method`
|
||||
- `request fields`
|
||||
- `required / optional`
|
||||
- `constraints`
|
||||
- `response`
|
||||
- `errors`
|
||||
- `idempotency`
|
||||
- `retry`
|
||||
- `timeout`
|
||||
- `auth`
|
||||
|
||||
## 3.15. Каталог ошибок
|
||||
|
||||
Ошибки, HTTP-коды, retry-правила и клиентское поведение не должны размазываться по разным документам.
|
||||
|
||||
Нужен единый каталог ошибок, например `docs/errors/catalog.yaml`.
|
||||
|
||||
Пример:
|
||||
|
||||
```yaml
|
||||
errors:
|
||||
- error_id: invoice_validation_failed
|
||||
http_code: 400
|
||||
internal_code: BILLING_400_01
|
||||
when: invalid request fields
|
||||
client_behavior: show field errors
|
||||
retry: false
|
||||
owner: billing
|
||||
|
||||
- error_id: invoice_duplicate
|
||||
http_code: 409
|
||||
internal_code: BILLING_409_01
|
||||
when: duplicate invoice detected
|
||||
client_behavior: show duplicate warning
|
||||
retry: false
|
||||
owner: billing
|
||||
|
||||
- error_id: crm_sync_unavailable
|
||||
http_code: 503
|
||||
internal_code: BILLING_503_02
|
||||
when: downstream CRM unavailable
|
||||
client_behavior: retry later
|
||||
retry: true
|
||||
owner: billing
|
||||
```
|
||||
|
||||
В API- и logic-документах лучше ссылаться на `error_id`, а не заново подробно описывать каждую ошибку.
|
||||
|
||||
## 3.16. Требования к качеству документа для RAG
|
||||
|
||||
1. **Явные заголовки** — не использовать безымянные блоки текста.
|
||||
2. **Атомарные утверждения** — не смешивать несколько правил в одном пункте, если их можно разделить.
|
||||
3. **Явные сущности** — использовать стабильные названия компонентов, API, модулей, страниц.
|
||||
4. **Явные ссылки** — не писать «этот метод», если можно указать конкретную ссылку или идентификатор.
|
||||
5. **Минимум дублирования** — повторяющийся контент должен заменяться ссылками.
|
||||
6. **Привязка к коду** — если документ описывает кодовый объект, это должно быть явно указано.
|
||||
7. **Разделение сценариев и правил** — workflow и fact-like требования должны быть отделены.
|
||||
|
||||
## 3.17. Как структура markdown помогает RAG
|
||||
|
||||
- `frontmatter` + заголовки → `D1_DOCUMENT_CATALOG`
|
||||
- `entities`, `tags`, устойчивые термины → `D3_ENTITY_CATALOG`
|
||||
- атомарные функциональные и нефункциональные требования → `D2_FACT_INDEX`
|
||||
- `Технический use case` → `D4_WORKFLOW_INDEX`
|
||||
- `related_docs`, явные ссылки → `D5_REFERENCE_GRAPH`
|
||||
- `related_code`, упоминания symbols и файлов → `D6_DOC_CODE_LINKS`
|
||||
- `Summary` → быстрый retrieval и short-form context для LLM
|
||||
|
||||
## 3.18. Принципы генерации документации агентом
|
||||
|
||||
Когда документ пишет агент, он должен:
|
||||
- сначала извлечь evidence из кода, системной аналитики и существующих документов;
|
||||
- определить тип документа;
|
||||
- заполнить frontmatter;
|
||||
- построить markdown body по шаблону;
|
||||
- явно указать связи с кодом и другими документами;
|
||||
- не дублировать уже существующее описание, если можно сослаться на него.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 4.4. Layered RAG
|
||||
|
||||
RAG строится как система специализированных слоев для двух основных доменов:
|
||||
- `CODE RAG`
|
||||
- `DOCS RAG`
|
||||
|
||||
Каждый graph извлекает контекст не из одного общего индекса, а из нужного набора слоев в зависимости от intent.
|
||||
|
||||
## 4.5. Evidence gate
|
||||
|
||||
Перед синтезом ответа или документа агент должен проверять, хватает ли опоры.
|
||||
|
||||
Примеры:
|
||||
- найден ли symbol;
|
||||
- найдено ли достаточное количество code chunks;
|
||||
- есть ли supporting relations;
|
||||
- есть ли document evidence;
|
||||
- есть ли docs ↔ code mapping.
|
||||
|
||||
Если опоры недостаточно, агент должен:
|
||||
- деградировать в упрощенный режим;
|
||||
- честно фиксировать неполноту ответа;
|
||||
- при необходимости уходить в fallback.
|
||||
|
||||
## 4.6. Synthesis layer
|
||||
|
||||
На этом этапе LLM:
|
||||
- агрегирует найденные артефакты;
|
||||
- формирует объяснение;
|
||||
- пишет документ;
|
||||
- структурирует результат под нужный шаблон.
|
||||
|
||||
LLM не должна быть основным источником фактов. Фактическая основа должна приходить из RAG и диагностируемого pipeline.
|
||||
|
||||
## 4.7. Diagnostics
|
||||
|
||||
Система должна сохранять диагностический след:
|
||||
- какой graph был выбран;
|
||||
- какие слои использовались;
|
||||
- что было найдено;
|
||||
- где retrieval был слабым;
|
||||
- почему был выбран fallback;
|
||||
- какие evidence стали основой ответа.
|
||||
|
||||
## 4.8. Сценарии: Target Architecture vs MVP-now
|
||||
|
||||
### 4.8.1. Target Architecture
|
||||
|
||||
#### CODE
|
||||
- `OPEN_FILE` — открыть конкретный файл;
|
||||
- `OPEN_SYMBOL` — открыть класс / функцию / метод;
|
||||
- `EXPLAIN` — объяснить, как работает сущность или фрагмент;
|
||||
- `FIND_TESTS` — найти релевантные тесты;
|
||||
- `FIND_ENTRYPOINTS` — найти основные точки входа;
|
||||
- `RELATED_CODE` — найти связанные сущности и ближайший контекст.
|
||||
|
||||
#### DOCS
|
||||
- `DOC_SEARCH` — найти релевантный фрагмент документации;
|
||||
- `DOC_EXPLAIN` — кратко объяснить, что сказано в документации по теме;
|
||||
- `DOC_ENTITY_LOOKUP` — найти разделы, связанные с сущностью или компонентом;
|
||||
- `GENERATE_DOCS_FROM_CODE` — сформировать документацию по коду с нуля для модуля, класса, функции, компонента или сценария.
|
||||
|
||||
#### CROSS-DOMAIN
|
||||
- `FIND_IMPLEMENTATION_BY_DOC` — найти реализацию по описанию;
|
||||
- `FIND_DOC_BY_CODE` — найти документацию по коду;
|
||||
- `COMPARE_DOCS_AND_CODE` — базовое сопоставление документации и реализации.
|
||||
|
||||
#### GENERAL / FALLBACK
|
||||
- `GENERAL_QA` — общий сценарий ответа на вопрос, если домен или интент не удалось определить уверенно.
|
||||
|
||||
### 4.8.2. MVP-now
|
||||
|
||||
В текущем цикле фокус на сценариях:
|
||||
|
||||
- `OPEN_FILE`
|
||||
- `EXPLAIN`
|
||||
- `FIND_TESTS`
|
||||
- `FIND_ENTRYPOINTS`
|
||||
- `GENERAL_QA`
|
||||
|
||||
DOCS и CROSS_DOMAIN остаются частью target architecture; в текущем цикле они не являются обязательной частью test-first MVP.
|
||||
|
||||
---
|
||||
|
||||
## 5. Структура слоев RAG
|
||||
|
||||
## 5.1. CODE RAG
|
||||
|
||||
### C0 — Source Chunks
|
||||
**Назначение:** базовые фрагменты исходного кода.
|
||||
**Единица:** chunk кода.
|
||||
**Как формируется:** исходные файлы обходятся и режутся на chunk’и с учетом структурных границ.
|
||||
**Статус в MVP:** да.
|
||||
|
||||
### C1 — Symbol Catalog
|
||||
**Назначение:** каталог модулей, классов, функций, методов и других значимых сущностей.
|
||||
**Единица:** symbol.
|
||||
**Как формируется:** из AST и синтаксического разбора кода.
|
||||
**Статус в MVP:** да.
|
||||
|
||||
### C2 — Symbol Relations
|
||||
**Назначение:** связи между symbols.
|
||||
**Единица:** relation.
|
||||
**Как формируется:** вторым проходом по AST и структурным зависимостям.
|
||||
**Статус в MVP:** да, в ограниченном виде.
|
||||
|
||||
### C3 — Entrypoints
|
||||
**Назначение:** каталог точек входа системы.
|
||||
**Единица:** entrypoint.
|
||||
**Как формируется:** специализированными детекторами entrypoint-паттернов.
|
||||
**Статус в MVP:** да, минимально.
|
||||
|
||||
### C4 — Execution Paths
|
||||
**Назначение:** типовые пути исполнения.
|
||||
**Единица:** path.
|
||||
**Как формируется:** поверх `C2` и `C3` через производную трассировку.
|
||||
**Статус в MVP:** нет.
|
||||
|
||||
### C5 — Test Mappings
|
||||
**Назначение:** связи production code ↔ tests.
|
||||
**Единица:** mapping.
|
||||
**Как формируется:** по путям, именам, импортам и конвенциям проекта.
|
||||
**Статус в MVP:** да, минимально.
|
||||
|
||||
### C6 — Code Facts
|
||||
**Назначение:** нормализованные факты из кода.
|
||||
**Единица:** fact.
|
||||
**Как формируется:** поверх `C1–C3` как производный слой.
|
||||
**Статус в MVP:** нет.
|
||||
|
||||
## 5.2. DOCS RAG
|
||||
|
||||
### D0 — Document Chunks
|
||||
**Назначение:** базовые фрагменты документации.
|
||||
**Единица:** document chunk.
|
||||
**Как формируется:** документы нормализуются и режутся на chunk’и с сохранением `section path`.
|
||||
**Статус в MVP:** да.
|
||||
|
||||
### D1 — Document Catalog
|
||||
**Назначение:** каталог документов и разделов.
|
||||
**Единица:** `document node / section node`.
|
||||
**Как формируется:** из структуры документов и их заголовков.
|
||||
**Статус в MVP:** да.
|
||||
|
||||
### D2 — Fact Index
|
||||
**Назначение:** атомарные факты из документации.
|
||||
**Единица:** fact.
|
||||
**Как формируется:** из `D0/D1` через правила, шаблоны и при необходимости LLM extraction с валидацией.
|
||||
**Статус в MVP:** частично.
|
||||
|
||||
### D3 — Entity Catalog
|
||||
**Назначение:** каталог сущностей и понятий документации.
|
||||
**Единица:** entity / concept.
|
||||
**Как формируется:** из устойчивых терминов, заголовков, словарей и нормализации повторяющихся сущностей.
|
||||
**Статус в MVP:** да, минимально.
|
||||
|
||||
### D4 — Workflow Index
|
||||
**Назначение:** процедуры, сценарии, последовательности шагов.
|
||||
**Единица:** workflow.
|
||||
**Как формируется:** из use case, процессных разделов и последовательных описаний шагов.
|
||||
**Статус в MVP:** нет.
|
||||
|
||||
### D5 — Reference Graph
|
||||
**Назначение:** граф ссылок между документами, секциями, сущностями и фактами.
|
||||
**Единица:** reference link.
|
||||
**Как формируется:** из явных и неявных cross-links между документами.
|
||||
**Статус в MVP:** нет.
|
||||
|
||||
### D6 — Doc-Code Links
|
||||
**Назначение:** мост между документацией и кодом.
|
||||
**Единица:** `doc artifact ↔ code artifact link`.
|
||||
**Как формируется:** из имен, aliases, путей, устойчивых терминов и других надежных соответствий.
|
||||
**Статус в MVP:** да, минимально.
|
||||
|
||||
## 5.3. Layer scope: Target Architecture vs MVP-now
|
||||
|
||||
### 5.3.1. Target Architecture
|
||||
|
||||
Полная карта слоёв:
|
||||
|
||||
- **CODE:** C0–C6 (Source Chunks, Symbol Catalog, Symbol Relations, Entrypoints, Execution Paths, Test Mappings, Code Facts)
|
||||
- **DOCS:** D0–D6 (Document Chunks, Document Catalog, Fact Index, Entity Catalog, Workflow Index, Reference Graph, Doc-Code Links)
|
||||
|
||||
### 5.3.2. MVP-now
|
||||
|
||||
**Обязательные сейчас:**
|
||||
|
||||
- `C0_SOURCE_CHUNKS`
|
||||
- `C1_SYMBOL_CATALOG`
|
||||
- `C2_SYMBOL_RELATIONS`
|
||||
- `C3_ENTRYPOINTS`
|
||||
|
||||
**В облегчённом виде:**
|
||||
|
||||
- `C5_TEST_MAPPINGS` или `C5-lite`
|
||||
|
||||
**Не блокируют текущий этап:**
|
||||
|
||||
- `C4_EXECUTION_PATHS`
|
||||
- `C6_CODE_FACTS`
|
||||
- весь docs runtime (слои D0–D6 в исполнении runtime)
|
||||
|
||||
Слои документации остаются частью target architecture; docs retrieval пока не обязателен для текущего code-first milestone.
|
||||
|
||||
---
|
||||
|
||||
## 6. Итоговая рамка MVP-now
|
||||
|
||||
Сейчас система должна стабильно работать в **test-first** режиме.
|
||||
|
||||
**Фокус:**
|
||||
|
||||
- CODE_QA;
|
||||
- через тесты настраиваются:
|
||||
- intent routing (IntentRouterV2);
|
||||
- layered retrieval;
|
||||
- evidence sufficiency;
|
||||
- answer quality;
|
||||
- diagnostics.
|
||||
|
||||
**Не входят в текущий milestone:**
|
||||
|
||||
- UI-интеграция;
|
||||
- docs runtime;
|
||||
- полная интеграция orchestration переносится на следующий этап после стабилизации test pipeline.
|
||||
|
||||
В целевой архитектуре по-прежнему заложены:
|
||||
- уверенная работа с кодом, symbols, entrypoints, тестами;
|
||||
- ответ по документации и мост docs ↔ code;
|
||||
- генерация документации по коду;
|
||||
- fallback при неуверенном роутинге.
|
||||
|
||||
В MVP-now сознательно **не включаются** самые дорогие части:
|
||||
- полноценные execution paths для всей системы;
|
||||
- богатые fact-индексы по всем доменам;
|
||||
- полный reference graph документации;
|
||||
- глубокая автоматизация подготовки системной аналитики.
|
||||
@@ -0,0 +1,33 @@
|
||||
Нужно реализовать 2 вещи
|
||||
|
||||
Создать процесс внесения изменений в файл документации
|
||||
Создать контекст этого процесса
|
||||
|
||||
Контекст наполнять атрибутами
|
||||
что-то явно задано, фоллбэк через ллм
|
||||
|
||||
|
||||
|
||||
Написать тестовую аналитику - круд над сущностью
|
||||
фронт, ефс, ппрб
|
||||
Все в своей БД
|
||||
Атрибуты сущности задать в требованиях
|
||||
|
||||
|
||||
|
||||
|
||||
Аналитика имеет структуру
|
||||
Внутри модули - один модуль на правку одного файла.
|
||||
|
||||
|
||||
Модуль извлекается из аналитики парсером и из него формируется задача на редактирование файла
|
||||
если парсер не сработал - фоллбэк ан ллм
|
||||
|
||||
|
||||
|
||||
Процесс редактирования работает стандартно
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Documentation Rules V3
|
||||
|
||||
Этот каталог содержит правила генерации технической документации из системной аналитики.
|
||||
|
||||
## Цель
|
||||
- синхронизировать требования к документации с требованиями к аналитике (`04. Analitycs artefacts - features.md`);
|
||||
- сохранить детальность техдокументации по сравнению с аналитикой;
|
||||
- убрать дублирование структуры и manifest-слоя между разными файлами;
|
||||
- собирать итоговый промпт из модулей: глобальные правила + template с manifest + блоки.
|
||||
|
||||
## Структура
|
||||
- `documentation-rules.md` — верхнеуровневый регламент и порядок сборки.
|
||||
- `global/` — общие правила (заголовки, frontmatter, слой ответственности, мост аналитика->документация).
|
||||
- `common-elements/` — правила для общих блоков (`summary`, `details`, `use case`, `FR`, `NFR`, `UI`, `Contract`).
|
||||
- `templates/` — единственный источник истины для структуры итоговой страницы и manifest-метаданных типа документа.
|
||||
|
||||
## Принцип сборки
|
||||
Для конкретного документа агент собирает единый набор правил из:
|
||||
1. `documentation-rules.md`
|
||||
2. `global/*.md`
|
||||
3. `templates/<doc_type>.template.md`
|
||||
4. `common-elements/*.md`, указанных в frontmatter template
|
||||
|
||||
## Правило без дублирования
|
||||
- `templates/` отвечают за структуру документа, порядок разделов и manifest-метаданные типа.
|
||||
- `common-elements/` отвечают только за правила написания конкретного раздела.
|
||||
- отдельный слой `types/` не нужен, если для типа документа используется один основной template.
|
||||
|
||||
## Формат template-manifest
|
||||
Manifest оформляется в YAML frontmatter самого template.
|
||||
|
||||
Обязательные поля manifest:
|
||||
- `doc_type`
|
||||
- `required_common_elements`
|
||||
|
||||
Рекомендуемые поля:
|
||||
- `special_rules`
|
||||
@@ -0,0 +1,21 @@
|
||||
# API Contract Rules
|
||||
|
||||
## Обязательные части
|
||||
- request parameters (`header/query/path`)
|
||||
- request body (если применимо)
|
||||
- response body
|
||||
- errors
|
||||
- auth
|
||||
- timeout
|
||||
- retry/idempotency (если применимо)
|
||||
|
||||
## Табличный формат
|
||||
Для request/response таблицы должны содержать:
|
||||
- название
|
||||
- тип данных
|
||||
- обязательность
|
||||
- описание
|
||||
- пример
|
||||
|
||||
Для response дополнительно:
|
||||
- заполнение (mapping/логика источника данных)
|
||||
@@ -0,0 +1,17 @@
|
||||
# DB Columns Rules
|
||||
|
||||
## Формат
|
||||
Структура таблицы оформляется таблицей.
|
||||
|
||||
## Обязательные колонки
|
||||
- `Поле`
|
||||
- `Тип`
|
||||
- `Nullable`
|
||||
- `Описание`
|
||||
- `Источник заполнения`
|
||||
- `Использование`
|
||||
|
||||
## Правила
|
||||
- перечислять все ключевые поля таблицы;
|
||||
- для служебных полей (`id`, `created_at`, `updated_at`, `deleted_at`) явно описывать назначение;
|
||||
- если тип или nullable не заданы в аналитике, допускается инженерное предположение с рабочим вариантом.
|
||||
@@ -0,0 +1,16 @@
|
||||
# DB Constraints Rules
|
||||
|
||||
## Что включать
|
||||
- primary key;
|
||||
- unique constraints;
|
||||
- foreign keys;
|
||||
- важные индексы;
|
||||
- бизнес-ограничения на уровне БД.
|
||||
|
||||
## Формат
|
||||
- списком или таблицей;
|
||||
- для каждого индекса и ограничения писать, зачем оно нужно.
|
||||
|
||||
## Правила
|
||||
- если индекс нужен для сценария чтения/пагинации, это должно быть явно сказано;
|
||||
- если точные названия индексов неизвестны, можно использовать осмысленные проектные названия.
|
||||
@@ -0,0 +1,12 @@
|
||||
# DB Table Purpose Rules
|
||||
|
||||
## Что описывать
|
||||
- назначение таблицы;
|
||||
- в каком сценарии она используется;
|
||||
- кто является владельцем данных;
|
||||
- является ли таблица источником истины или производным хранилищем.
|
||||
|
||||
## Формат
|
||||
- 1-3 абзаца без воды;
|
||||
- явно указывать доменную сущность, которую хранит таблица;
|
||||
- если сделаны допущения по БД, фиксировать их отдельной фразой.
|
||||
@@ -0,0 +1,11 @@
|
||||
# DB Usage Rules
|
||||
|
||||
## Что описывать
|
||||
- какие API / logic block / batch job используют таблицу;
|
||||
- какие операции выполняются: read / insert / update / delete;
|
||||
- как таблица участвует в пользовательском сценарии.
|
||||
|
||||
## Правила
|
||||
- ссылки на связанные документы давать по `doc_id` или path;
|
||||
- не дублировать полный use case, а показывать роль таблицы в сценарии;
|
||||
- если таблица используется для пагинации, фильтрации или сортировки, это нужно отметить явно.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Details Rules
|
||||
|
||||
## Назначение
|
||||
Этот файл задает общие правила для секции `## Details`.
|
||||
|
||||
## Правила
|
||||
- `Details` оформляется как `## Details`.
|
||||
- Внутри `Details` используются заголовки уровня `###` и ниже.
|
||||
- Структура `Details` определяется template типа документа.
|
||||
- В `Details` не нужно дублировать навигацию и связи, если они уже есть во frontmatter.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Functional Requirements Rules
|
||||
|
||||
## Формат
|
||||
- `FR.<номер>. <Название>`
|
||||
- Нумерация инкрементальная внутри документа.
|
||||
|
||||
## Правила
|
||||
- FR расширяют шаги сценария.
|
||||
- FR не копируют шаги сценария без добавления новой информации.
|
||||
- Для интеграционных шагов FR обязательны.
|
||||
- Если в сценарии есть вызов внешнего API / сервиса / БД, нужен отдельный FR на интеграцию.
|
||||
|
||||
## FR для интеграционных шагов
|
||||
Для интеграционного FR обязательно раскрывать:
|
||||
- как формируется запрос;
|
||||
- откуда берется каждый значимый атрибут запроса;
|
||||
- какой downstream вызывается;
|
||||
- какой ответ считается успешным;
|
||||
- какие ответы и ситуации считаются бизнес-ошибкой;
|
||||
- какие ситуации считаются технической ошибкой;
|
||||
- как downstream-ответ маппится в контракт текущего слоя.
|
||||
|
||||
## FR для шагов доступа к БД
|
||||
Если шаг читает или пишет БД, FR должен по возможности включать:
|
||||
- таблицу или набор таблиц;
|
||||
- логику фильтрации;
|
||||
- логику сортировки;
|
||||
- логику пагинации;
|
||||
- пример SQL или близкий к рабочему псевдо-SQL.
|
||||
|
||||
Если СУБД и диалект не заданы, допускается сделать рабочее предположение и явно зафиксировать его.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Non-Functional Requirements Rules
|
||||
|
||||
## Для api_method
|
||||
- Подразделы:
|
||||
- `#### Аудит` (если применимо)
|
||||
- `#### Мониторинг`
|
||||
|
||||
## Мониторинг
|
||||
Оформлять таблицей:
|
||||
- `Метрика`
|
||||
- `Описание`
|
||||
- `Условие срабатывания`
|
||||
|
||||
Запрещено:
|
||||
- использовать «точка измерения = метод» вместо условий срабатывания.
|
||||
|
||||
Базовые суффиксы метрик:
|
||||
- `_SUCCESS`
|
||||
- `_FAIL`
|
||||
- `_BUSINESS_ERROR`
|
||||
@@ -0,0 +1,15 @@
|
||||
# SQL Example Rules
|
||||
|
||||
## Назначение
|
||||
Секция показывает пример рабочего SQL для основного сценария использования таблицы.
|
||||
|
||||
## Правила
|
||||
- SQL должен быть близок к рабочему, а не абстрактным псевдокодом;
|
||||
- если диалект БД не указан, допускается выбрать наиболее вероятный вариант и явно зафиксировать допущение;
|
||||
- пример должен отражать реальный сценарий документа: чтение, вставка, обновление или агрегация;
|
||||
- для read-сценариев по возможности показывать фильтрацию, сортировку и пагинацию;
|
||||
- если есть join, нужно кратко пояснить, зачем он нужен.
|
||||
|
||||
## Формат
|
||||
- fenced code block с указанием `sql`;
|
||||
- под кодом 1-3 поясняющих bullets о ключевых условиях, индексах и параметрах.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Summary Rules
|
||||
|
||||
## Назначение
|
||||
Этот файл задает правила для секции `## Summary`.
|
||||
|
||||
## Правила
|
||||
- `Summary` должен быть коротким слоем быстрого контекста.
|
||||
- `Summary` должен объяснять суть документа без длинных деталей.
|
||||
- Предпочтительный формат: краткий список ключевых фактов.
|
||||
- `Summary` не должен дублировать `Details`.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Tech Use Case Rules
|
||||
|
||||
## Обязательные части
|
||||
- название
|
||||
- предусловия
|
||||
- триггер
|
||||
- основной сценарий
|
||||
- альтернативный сценарий
|
||||
- обработка ошибок
|
||||
- постусловие
|
||||
|
||||
## Правила шага
|
||||
- Один шаг = одно предложение до 15-20 слов.
|
||||
- Формат шага: смысловое действие + техническая реализация (endpoint/топик/операция).
|
||||
- Длинные технические детали выносить в FR и ссылаться на FR из шага.
|
||||
- Для интеграционных шагов описание обработки ошибок обязательно.
|
||||
@@ -0,0 +1,22 @@
|
||||
# UI Requirements Rules
|
||||
|
||||
## Структура блока
|
||||
- `### Требования к UI`
|
||||
- Внутри обязательно отдельные формы:
|
||||
- табличное представление
|
||||
- пустой список (empty state)
|
||||
- ошибка (error state)
|
||||
|
||||
## Обязательные правила
|
||||
- Если есть интеграция, обязательно описывать показ ошибки.
|
||||
- Если есть список, обязательно описывать показ отсутствия данных.
|
||||
|
||||
## Описание UI-элементов
|
||||
UI-элементы описываются строго в таблице.
|
||||
|
||||
Обязательные колонки (где применимо):
|
||||
- `Код элемента`
|
||||
- `Название и описание`
|
||||
- `Данные`
|
||||
- `Поведение`
|
||||
- `Валидация`
|
||||
@@ -0,0 +1,7 @@
|
||||
# User Analytics Rules
|
||||
|
||||
События пользовательской аналитики оформлять таблицей:
|
||||
- `Название события`
|
||||
- `Описание`
|
||||
- `Точка вызова`
|
||||
- `Payload`
|
||||
@@ -0,0 +1,45 @@
|
||||
# Documentation Rules V3
|
||||
|
||||
## 1. Общий контракт
|
||||
- Документация строится на основе системной аналитики, но на более детальном уровне.
|
||||
- Заголовки отражают только суть раздела; метаданные в заголовках запрещены.
|
||||
- Метаданные указываются во frontmatter и/или отдельными строками в body.
|
||||
- Структура документа определяется только template соответствующего типа.
|
||||
- Правила написания конкретного раздела определяются только соответствующим `common-elements` файлом.
|
||||
- Manifest типа документа хранится во frontmatter соответствующего template.
|
||||
|
||||
## 2. Источники требований
|
||||
При генерации документа учитывать:
|
||||
- `/Users/alex/Dev_projects_v2/ai driven app process/v2/agent/_process/04. Analitycs artefacts - documentation.md`
|
||||
- `/Users/alex/Dev_projects_v2/ai driven app process/v2/agent/_process/04. Analitycs artefacts - features.md`
|
||||
- правила v2 из `src/app/core/agent/processes/v2/doc_rules_v2`
|
||||
|
||||
## 3. Разрыв аналитика vs документация
|
||||
- Аналитика: концептуальная, укрупненная.
|
||||
- Документация: технически детальная.
|
||||
- Технический use case в документации не копирует аналитический 1-в-1, а детализирует его.
|
||||
- Функциональные требования расширяют сценарий и не дублируют шаги без новой информации.
|
||||
|
||||
## 4. Заполнение пробелов
|
||||
Если атрибуты/детали отсутствуют в аналитике:
|
||||
1. восстановить из формулировок аналитики;
|
||||
2. уточнить по репозиторию (код, контракты, существующие документы);
|
||||
3. зафиксировать в документации явно.
|
||||
|
||||
## 5. Сборка итогового промпта
|
||||
1. Загрузить global-правила.
|
||||
2. Загрузить template типа документа.
|
||||
3. Прочитать YAML frontmatter template как manifest.
|
||||
4. Загрузить общие блоки, указанные в manifest.
|
||||
5. Применить body template как единственный источник структуры.
|
||||
5. Проверить чек-лист совместимости с аналитикой (domain/sub_domain, роли слоев, интеграции, ошибки).
|
||||
|
||||
## 6. Формат manifest типа документа
|
||||
Manifest типа документа хранится во frontmatter `templates/<doc_type>.template.md`.
|
||||
|
||||
Минимальная схема:
|
||||
- `doc_type`
|
||||
- `required_common_elements`
|
||||
|
||||
Дополнительно можно указывать:
|
||||
- `special_rules`
|
||||
@@ -0,0 +1,10 @@
|
||||
# Analytics to Documentation Mapping
|
||||
|
||||
## Принцип
|
||||
- Системная аналитика задает «что».
|
||||
- Документация детализирует «как».
|
||||
|
||||
## Маппинг
|
||||
- Из раздела архитектуры аналитики переносить контейнеры, интеграции и цепочки вызовов.
|
||||
- Из раздела изменений аналитики строить отдельные технические страницы (`ui_page`, `api_method`, `logic_block`).
|
||||
- Если в аналитике упрощенный use case, в документации раскрывать полный технический сценарий по правилам `tech-use-case.md`.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Правила определения путей файлов
|
||||
|
||||
Текущая happy-path реализация строит путь документа по фиксированному шаблону:
|
||||
|
||||
`docs/<domain>/<platform>/<doc_type>/<doc_id>.md`
|
||||
|
||||
Пример:
|
||||
|
||||
`docs/orders/pprb/ui_page/orders.ui.list.md`
|
||||
|
||||
## Источники атрибутов
|
||||
|
||||
Для построения пути используются четыре основных атрибута:
|
||||
|
||||
- `domain`
|
||||
- `application`
|
||||
- `platform`
|
||||
- `doc_type`
|
||||
- `id` как `doc_id`
|
||||
|
||||
Если атрибуты явно указаны в подразделе `6.x`, нужно использовать их.
|
||||
Если атрибут не указан, он может быть взят из общих метаданных аналитики или определен fallback-логикой.
|
||||
|
||||
## Нормализация сегментов
|
||||
|
||||
Каждый сегмент пути нормализуется одинаково:
|
||||
|
||||
- значение переводится в lowercase;
|
||||
- все символы, кроме `a-z`, `0-9`, `.`, `_`, `-`, заменяются на `-`;
|
||||
- ведущие и хвостовые `.` и `-` удаляются.
|
||||
|
||||
Примеры нормализации:
|
||||
|
||||
- `Payment Status` -> `payment-status`
|
||||
- `UFS Orders` -> `ufs-orders`
|
||||
- `crm.mobile` -> `crm.mobile`
|
||||
|
||||
## Значения по умолчанию
|
||||
|
||||
Если после нормализации сегмент пустой, используются fallback-значения:
|
||||
|
||||
- корневая папка: `domain`, иначе `application`, иначе `common`
|
||||
- `platform` -> `web`
|
||||
- `doc_type` -> `misc`
|
||||
- `doc_id` -> `untitled`
|
||||
|
||||
## Что важно в текущей версии
|
||||
|
||||
- для корневой папки сначала используется `domain`;
|
||||
- если `domain` не задан, используется `application`;
|
||||
- `sub_domain` сейчас не участвует в построении пути;
|
||||
- операции `create`, `update`, `delete` работают с одним и тем же правилом вычисления пути;
|
||||
- специальных исключений для разных типов документов пока нет;
|
||||
- отдельные каталоги для `pprb`, `ufs`, `web` задаются только через значение `platform`.
|
||||
|
||||
## Практическое правило для агента
|
||||
|
||||
Если нужно предложить или определить путь новой страницы, агент должен:
|
||||
|
||||
1. определить `application`;
|
||||
2. определить `domain`;
|
||||
3. определить `platform`;
|
||||
4. определить `doc_type`;
|
||||
5. определить стабильный `doc_id`;
|
||||
6. взять корневую папку как `domain`, а если он пустой, то `application`;
|
||||
7. нормализовать все сегменты;
|
||||
8. собрать путь по шаблону `docs/<root>/<platform>/<doc_type>/<doc_id>.md`.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Frontmatter Rules
|
||||
|
||||
## Обязательные поля
|
||||
```yaml
|
||||
id: string
|
||||
title: string
|
||||
doc_type: string
|
||||
domain: string
|
||||
sub_domain: string
|
||||
related_docs: []
|
||||
status: string
|
||||
```
|
||||
|
||||
## Рекомендуемые поля
|
||||
```yaml
|
||||
tags: []
|
||||
entities: []
|
||||
source_of_truth: string
|
||||
related_code: []
|
||||
system_analytics_refs: []
|
||||
```
|
||||
|
||||
## Body-метаданные для секции изменений
|
||||
Под корнем секции изменений указывать:
|
||||
- `domain`
|
||||
- `sub_domain`
|
||||
|
||||
Для каждого подраздела `X.Y` указывать строками:
|
||||
- `id`
|
||||
- `doc_type`
|
||||
- `application`
|
||||
- `platform`
|
||||
@@ -0,0 +1,10 @@
|
||||
# Header Rules
|
||||
|
||||
## Правила
|
||||
- Заголовок описывает только смысл раздела.
|
||||
- Не включать в заголовок: `id`, `doc_type`, `application`, `platform`, `domain`, `sub_domain`.
|
||||
- Метаданные указываются отдельными строками ниже заголовка или во frontmatter.
|
||||
|
||||
## Пример
|
||||
- Правильно: `## 6.2 Метод UFS получения списка заказов`
|
||||
- Неправильно: `## 6.2 Блок api_method (id=..., platform=ufs)`
|
||||
@@ -0,0 +1,10 @@
|
||||
# Layer Responsibility
|
||||
|
||||
- `ui`: отображение, UX, запуск пользовательских сценариев.
|
||||
- `ufs`: авторизация/аутентификация, агрегация, маппинг, оркестрация вызовов.
|
||||
- `pprb`: API, БД, доменная логика backend.
|
||||
|
||||
## Правила согласованности
|
||||
- Проверка ролевой модели пользователя обычно фиксируется на уровне `ufs`.
|
||||
- Если проверка роли вынесена в `ufs`, в `pprb`-сценарии не дублировать этот шаг.
|
||||
- Аудит для `pprb` может отсутствовать, если это явно принято для домена/фичи.
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
doc_type: api_method
|
||||
required_common_elements:
|
||||
- common-elements/summary.md
|
||||
- common-elements/details.md
|
||||
- common-elements/tech-use-case.md
|
||||
- common-elements/fr.md
|
||||
- common-elements/nfr.md
|
||||
- common-elements/api-contract.md
|
||||
special_rules:
|
||||
- Технический use case детализируется по `common-elements/tech-use-case.md`.
|
||||
- FR расширяют use case и не дублируют шаги сценария без новой информации.
|
||||
- Для интеграционных шагов FR обязательны.
|
||||
---
|
||||
|
||||
# <title>
|
||||
|
||||
## Summary
|
||||
Правила оформления: `../common-elements/summary.md`
|
||||
|
||||
## Details
|
||||
Правила оформления: `../common-elements/details.md`
|
||||
|
||||
### Технический use case
|
||||
Правила оформления: `../common-elements/tech-use-case.md`
|
||||
|
||||
### Функциональные требования
|
||||
Правила оформления: `../common-elements/fr.md`
|
||||
|
||||
### Нефункциональные требования
|
||||
Правила оформления: `../common-elements/nfr.md`
|
||||
|
||||
### Контракт
|
||||
Правила оформления: `../common-elements/api-contract.md`
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
doc_type: db_table
|
||||
required_common_elements:
|
||||
- common-elements/summary.md
|
||||
- common-elements/details.md
|
||||
- common-elements/db-purpose.md
|
||||
- common-elements/db-columns.md
|
||||
- common-elements/db-constraints.md
|
||||
- common-elements/db-usage.md
|
||||
- common-elements/sql-example.md
|
||||
special_rules:
|
||||
- Документ описывает одну физическую таблицу БД или materialized view.
|
||||
- Нужно фиксировать назначение таблицы, поля, ограничения, индексы, связи и сценарии использования.
|
||||
- Если точные детали БД не заданы, допустимо сделать рабочие инженерные допущения и явно записать их в документ.
|
||||
---
|
||||
|
||||
# <title>
|
||||
|
||||
## Summary
|
||||
Правила оформления: `../common-elements/summary.md`
|
||||
|
||||
## Details
|
||||
Правила оформления: `../common-elements/details.md`
|
||||
|
||||
### Назначение таблицы
|
||||
Правила оформления: `../common-elements/db-purpose.md`
|
||||
|
||||
### Структура таблицы
|
||||
Правила оформления: `../common-elements/db-columns.md`
|
||||
|
||||
### Ограничения и индексы
|
||||
Правила оформления: `../common-elements/db-constraints.md`
|
||||
|
||||
### Использование в сценариях
|
||||
Правила оформления: `../common-elements/db-usage.md`
|
||||
|
||||
### Пример SQL
|
||||
Правила оформления: `../common-elements/sql-example.md`
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
doc_type: logic_block
|
||||
required_common_elements:
|
||||
- common-elements/summary.md
|
||||
- common-elements/details.md
|
||||
- common-elements/tech-use-case.md
|
||||
- common-elements/fr.md
|
||||
- common-elements/nfr.md
|
||||
special_rules:
|
||||
- Logic block описывает переиспользуемую логику без дублирования UI/API деталей.
|
||||
---
|
||||
|
||||
# <title>
|
||||
|
||||
## Summary
|
||||
Правила оформления: `../common-elements/summary.md`
|
||||
|
||||
## Details
|
||||
Правила оформления: `../common-elements/details.md`
|
||||
|
||||
### Технический use case
|
||||
Правила оформления: `../common-elements/tech-use-case.md`
|
||||
|
||||
### Функциональные требования
|
||||
Правила оформления: `../common-elements/fr.md`
|
||||
|
||||
### Нефункциональные требования
|
||||
Правила оформления: `../common-elements/nfr.md`
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
doc_type: ui_page
|
||||
required_common_elements:
|
||||
- common-elements/summary.md
|
||||
- common-elements/details.md
|
||||
- common-elements/tech-use-case.md
|
||||
- common-elements/ui-requirements.md
|
||||
- common-elements/fr.md
|
||||
- common-elements/user-analytics.md
|
||||
special_rules:
|
||||
- Для списочных страниц обязательно описывать табличное представление, empty state и error state.
|
||||
- UI-элементы описываются в таблицах по правилам `common-elements/ui-requirements.md`.
|
||||
---
|
||||
|
||||
# <title>
|
||||
|
||||
## Summary
|
||||
Правила оформления: `../common-elements/summary.md`
|
||||
|
||||
## Details
|
||||
Правила оформления: `../common-elements/details.md`
|
||||
|
||||
### Технический use case
|
||||
Правила оформления: `../common-elements/tech-use-case.md`
|
||||
|
||||
### Требования к UI
|
||||
Правила оформления: `../common-elements/ui-requirements.md`
|
||||
|
||||
### Функциональные требования
|
||||
Правила оформления: `../common-elements/fr.md`
|
||||
|
||||
### Нефункциональные требования
|
||||
Правила оформления: `../common-elements/user-analytics.md`
|
||||
@@ -1,3 +1,9 @@
|
||||
from app.core.agent.runtime import AgentRuntime
|
||||
|
||||
__all__ = ["AgentRuntime"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
if name == "AgentRuntime":
|
||||
from app.core.agent.runtime import AgentRuntime
|
||||
|
||||
return AgentRuntime
|
||||
raise AttributeError(name)
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
from app.core.agent.processes.base import AgentProcess, ProcessResult
|
||||
from app.core.agent.processes.v1.process import V1Process
|
||||
from app.core.agent.processes.v2.v2_process import V2Process
|
||||
|
||||
__all__ = [
|
||||
"AgentProcess",
|
||||
"ProcessResult",
|
||||
"V1Process",
|
||||
"V2Process",
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
if name in {"AgentProcess", "ProcessResult"}:
|
||||
from app.core.agent.processes.base import AgentProcess, ProcessResult
|
||||
|
||||
return {"AgentProcess": AgentProcess, "ProcessResult": ProcessResult}[name]
|
||||
if name == "V1Process":
|
||||
from app.core.agent.processes.v1.process import V1Process
|
||||
|
||||
return V1Process
|
||||
if name == "V2Process":
|
||||
from app.core.agent.processes.v2.v2_process import V2Process
|
||||
|
||||
return V2Process
|
||||
raise AttributeError(name)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from app.core.agent.processes.v2.intent_router.router import V2IntentRouter
|
||||
|
||||
__all__ = ["V2IntentRouter", "V2Process"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
if name == "V2IntentRouter":
|
||||
from app.core.agent.processes.v2.intent_router.router import V2IntentRouter
|
||||
|
||||
return V2IntentRouter
|
||||
if name == "V2Process":
|
||||
from app.core.agent.processes.v2.v2_process import V2Process
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# Documentation Rules V2
|
||||
|
||||
Этот каталог — новая структура правил для DOC_UPDATE/FROM_FEATURE.
|
||||
|
||||
## Цель
|
||||
|
||||
Разделить инструкции на 2 независимых блока:
|
||||
|
||||
1. `types/` — инструкции по структуре конкретных типов документов (`ui_page`, `api_method`, и т.д.).
|
||||
2. `common-elements/` — инструкции по структуре общих элементов страницы (`tech-use-case`, `fr`, и др.).
|
||||
|
||||
## Структура каталога
|
||||
|
||||
- `documentation-rules.md` — верхнеуровневые правила.
|
||||
- `global/` — общие правила оформления и frontmatter.
|
||||
- `types/` — правила по типам документов (вместо `artifact-types/`).
|
||||
- `common-elements/` — правила по общим секциям (вместо `sections/`).
|
||||
- `templates/` — шаблоны документов.
|
||||
|
||||
## Переключение профиля
|
||||
|
||||
Загрузчик поддерживает переключение через env:
|
||||
|
||||
- `DOC_RULES_PROFILE=v2` (по умолчанию) — использовать `doc_rules_v2`.
|
||||
- `DOC_RULES_PROFILE=legacy` — использовать старый каталог `doc_rules`.
|
||||
|
||||
Если `v2` не содержит валидных пар `type + template`, загрузчик автоматически fallback на `doc_rules`.
|
||||
@@ -1,24 +0,0 @@
|
||||
# API Contract Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл описывает, как оформлять подраздел `## Контракт` в API-документах.
|
||||
|
||||
## Что должно быть описано
|
||||
|
||||
- входные параметры
|
||||
- выходные параметры
|
||||
- JSON-структуры запросов и ответов
|
||||
- обязательность полей
|
||||
- типы полей
|
||||
- ограничения
|
||||
- описание назначения полей
|
||||
- примеры данных
|
||||
- auth
|
||||
- idempotency
|
||||
- timeout
|
||||
- ошибки и их HTTP-коды
|
||||
|
||||
## Правило качества
|
||||
|
||||
Контракт должен быть достаточно формальным, чтобы по нему можно было собрать OpenAPI-спецификацию.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Details Section Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает общие правила для секции `## Details`.
|
||||
|
||||
## Правила
|
||||
|
||||
- `Details` оформляется как `## Details`.
|
||||
- Внутри `Details` используются заголовки уровня `###` и ниже.
|
||||
- Структура Details зависит от типа документа.
|
||||
- В Details не нужно повторно дублировать навигацию и связи, если они уже есть во frontmatter.
|
||||
- Интеграции, ошибки и кодовые привязки должны быть выделены в отдельные подразделы, если они существенны для понимания документа.
|
||||
@@ -1,37 +0,0 @@
|
||||
# Functional requrements rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл описывает, как оформлять функциональные требования в подраздел `### Функциональные требования` в документах.
|
||||
|
||||
## Правила
|
||||
- Функциональное требование (FR) расширяет и дополняет шаги, описанные в сценарии.
|
||||
- Функциональное требование (FR) не должно копировать шаг сценария не неся дополнительной информации.
|
||||
- Название функционального требования формируется следующим образом - "FR.<номер>. <Название>", где
|
||||
- <номер> идет инкрементально внутри конкретного документа, начинается с 1.
|
||||
- <Название> - кратко описывает что делает требование, суть действий (от 3 до 7 слов)
|
||||
|
||||
|
||||
|
||||
## Пример целевого описания сценария
|
||||
|
||||
### Примеры названия FR
|
||||
- Получение данных клиента из АС ЕПК
|
||||
- Проверка уровня доступа
|
||||
- Сценарий построения списка связанных предложений
|
||||
|
||||
|
||||
### Примеры описания FR
|
||||
FR.1. Получение данных клиента из АС ЕПК
|
||||
1. Сформировать запрос к эндпоинту POST /api/v1/path/to/resourse в АС ЕПК
|
||||
- Заголовки
|
||||
- <тут идет описание заголовков и того как они формируются>
|
||||
- Параметры запроса
|
||||
- <тут идет описание параметров и того как они формируются>
|
||||
- Тело запроса
|
||||
- <тут идет описание структуры объекта JSON или payload в другмо формате так как это задано требованиями>
|
||||
|
||||
2. Обработать ответ от АС ЕПК
|
||||
Успешный ответ - <взять из описания вызываеого api критерии успешного ответа >
|
||||
Ничего не найдено - <взять из описания вызываеого api критерии успешного овтета, опционально (если применимо)>
|
||||
Ошибка - <взять из описания вызываеого api критерии успешного ответа >
|
||||
@@ -1,16 +0,0 @@
|
||||
# Requirements Format Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает формат для функциональных и нефункциональных требований.
|
||||
|
||||
## Функциональные требования
|
||||
|
||||
- Использовать коды `FR-1`, `FR-2`, `FR-3` и так далее.
|
||||
- Каждое требование должно описывать отдельный обязательный аспект поведения.
|
||||
- Идентификаторы локальны в пределах одного документа.
|
||||
|
||||
## Нефункциональные требования
|
||||
|
||||
- Использовать коды `NFR-1`, `NFR-2`, `NFR-3` и так далее.
|
||||
- Требования должны описывать характеристики качества, ограничения и эксплуатационные свойства.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Summary Section Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает правила для секции `## Summary`.
|
||||
|
||||
## Правила
|
||||
|
||||
- Summary должен быть коротким explain-слоем быстрого контекста.
|
||||
- Summary должен объяснять суть документа без лишних деталей.
|
||||
- Summary должен быть пригоден для explain и быстрого чтения.
|
||||
- Предпочтительный формат: список ключевых фактов `Purpose`, `Actor`, `Trigger`, `Errors`, `Related ...` и т.д.
|
||||
- Для крупных документов допустим более длинный summary, если он остается структурированным.
|
||||
@@ -1,66 +0,0 @@
|
||||
# Scenario Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл описывает, как оформлять технический USE CASE в подраздел `### Сценарий` в документах.
|
||||
|
||||
## Обязательные части
|
||||
|
||||
- название
|
||||
- предусловия
|
||||
- триггер
|
||||
- основной сценарий
|
||||
- альтернативный сценарий
|
||||
- обработка ошибок
|
||||
- постусловие
|
||||
|
||||
## Правила
|
||||
- Основной и альтернативные сценарии состоят из шагов.
|
||||
|
||||
- Каждый шаг описывается одним предложением не более 15-20 слов, и состоит из двух частей. Первая часть описывает что мы делаем по смыслу, чтобы это было понятно человеку без низкоуровневых технических деталей. Например: авторизует запрос, получает данные клиента, запрашивает справочники. Вторая часть описывает как это реализовано технически - вызывает эндпоинт /path/to/resource в системе <название системы>.
|
||||
|
||||
- В описании шага не должно быть длинных технических деталей. Если техничсекую реализацию нельхзя описатьодним предложенеим (в лимите длины описания шага), то необхлодимо это вынести в отдельное функциональное требование FR.<номер>. <Название> и описать в нем технические детали. А в шаге сослаться на это требование через "Описание приведено в FR.<номер>. <Название>"
|
||||
|
||||
- Для шагов авторизации обязателен доп шаг с описанием обработки ошибки.
|
||||
- Для шагов с интеграцией обязателен доп шаг с описанием обработки ошибки.
|
||||
- Для шагов с проверкой условий обязательны доп шаги с описанием переходов по сценарию.
|
||||
|
||||
- Название "FR.<номер>. <Название>" формируется следующим образом:
|
||||
- <номер> идет инкрементально внутри конкретного документа, начинается с 1.
|
||||
- <Название> - кратко описывает что делает требование, суть действий.
|
||||
|
||||
- Для каждого шага при необходимости нужно прописать логику действий в случае ошибки или если логика шага определяет несколько сценариев разивития при выполнении заданных условий.
|
||||
|
||||
- Для шагов, которые описывают интеграцию с другой системой необходимо указать название точки интеграции (название эндпоинта, название топика и так далее) и сделать ссылку на FR.<номер>. <Название> с описанием шагов интеграции - как сформировать запрос/сообщение, как обработать ответ, политику ретраев.
|
||||
|
||||
- Сценарий собирается из тезисов, приведенных системной аналимтике в свободной формулировке
|
||||
|
||||
- Функциональные требования "FR.<номер>. <Название>" не должны дублировать шагов сценария в use case. Они содержат детали, которые вынесены из юзкейса чтобы не делать его тяжелым. Если шаг юзкейса описывается одним предложением в лимите длины, то FR делать не нужно.
|
||||
|
||||
- FR обязательно описывается для шага с интеграцией
|
||||
- FR Не описывается для шага авторизации.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Пример целевого описания сценария
|
||||
|
||||
### Примеры шагов сценария
|
||||
|
||||
Пример 1
|
||||
- Авторизует запрос пользователя по наличию у него экшена ролевой модели CI02792632.ContactsDGR.Detail
|
||||
- В случае ошибки - завершить сценарий с кодом UNAUTHORIZED
|
||||
|
||||
Пример 2
|
||||
- Запрашивает данные клиента - вызывает /api/v1/clients/{client-id}/info
|
||||
- В случае ошибки - завершить сценарий с кодом CLIENT_INFO_REQUEST_FAIL
|
||||
|
||||
Пример 3
|
||||
- Возвращает ответ в формате <название DTO>
|
||||
|
||||
### Примеры названия FR
|
||||
- Получение данных клиента из АС ЕПК
|
||||
- Проверка уровня доступа
|
||||
- Сценарий построения списка связанных предложений
|
||||
@@ -1,71 +0,0 @@
|
||||
# Documentation Rules
|
||||
|
||||
Этот каталог оформляет MVP документации проекта в атомарном формате.
|
||||
|
||||
## Базовая структура
|
||||
|
||||
- Каждый документ содержит YAML frontmatter.
|
||||
- В документе должен быть один `H1`, совпадающий с `title`.
|
||||
- Основные разделы оформляются как `## Summary` и `## Details`.
|
||||
- Внутри `Details` используются заголовки уровня `###` и ниже.
|
||||
- Связи, сущности и навигация описываются во frontmatter через `related_docs`, `links`, `entities`, `parent`, `children`.
|
||||
|
||||
## Summary
|
||||
|
||||
- Краткий explain-слой быстрого контекста.
|
||||
- Должен позволять быстро понять назначение документа без чтения `Details`.
|
||||
- Предпочтительный формат: компактный список ключевых фактов без длинных абзацев.
|
||||
|
||||
## Details
|
||||
|
||||
- Раскрывает полное описание объекта.
|
||||
- Структура `Details` зависит от типа документа.
|
||||
- Сценарии, ограничения, интеграции, ошибки и кодовые привязки должны быть разнесены по отдельным подразделам.
|
||||
|
||||
## API documents
|
||||
|
||||
Для `api_method` внутри `## Details` обязательны разделы:
|
||||
- `### Описание`
|
||||
- `### Сценарий`
|
||||
- `### Функциональные требования`
|
||||
- `### Нефункциональные требования`
|
||||
- `### Контракт`
|
||||
|
||||
Если у метода есть интеграции и ошибки, также обязательны:
|
||||
- `### Интеграции`
|
||||
- `### Ошибки`
|
||||
- `### Связанный код`
|
||||
- `### История изменений`
|
||||
|
||||
### Сценарий
|
||||
|
||||
Сценарий оформляется как технический use case и содержит:
|
||||
- название
|
||||
- предусловия
|
||||
- триггер
|
||||
- основной сценарий
|
||||
- альтернативный сценарий
|
||||
- обработку ошибок
|
||||
- постусловие
|
||||
|
||||
### Требования
|
||||
|
||||
- Функциональные требования маркируются как `FR-1`, `FR-2`, ...
|
||||
- Нефункциональные требования маркируются как `NFR-1`, `NFR-2`, ...
|
||||
- Идентификаторы требований локальны в рамках одного документа.
|
||||
|
||||
### Контракт
|
||||
|
||||
Контракт должен быть пригоден для последующей сборки OpenAPI-спецификации и включать:
|
||||
- входные параметры
|
||||
- выходные параметры
|
||||
- структуру JSON-сообщений
|
||||
- обязательность полей
|
||||
- типы и ограничения
|
||||
- описание полей
|
||||
- правила заполнения
|
||||
- примеры данных
|
||||
- auth
|
||||
- idempotency
|
||||
- timeout
|
||||
- ошибки и их HTTP-коды
|
||||
@@ -1,38 +0,0 @@
|
||||
# Documentation System
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает общую модель документации проекта.
|
||||
|
||||
## Базовая модель
|
||||
|
||||
Каждый документ должен состоять из двух слоев:
|
||||
- YAML frontmatter
|
||||
- контент
|
||||
|
||||
Контент всегда состоит из двух обязательных разделов:
|
||||
- `## Summary`
|
||||
- `## Details`
|
||||
|
||||
Над ними должен быть один заголовок `# <title>`, совпадающий со значением `title` во frontmatter.
|
||||
|
||||
## Принципы
|
||||
|
||||
- Документы должны быть атомарными.
|
||||
- Один документ описывает одну тему.
|
||||
- Вместо дублирования между документами используются явные ссылки.
|
||||
- Связи и навигация должны быть формализованы.
|
||||
- Документы должны быть пригодны для чтения человеком и для RAG.
|
||||
- Документы должны быть пригодны для частичного обновления без деградации структуры.
|
||||
|
||||
## Типы документов
|
||||
|
||||
На уровне проекта поддерживаются типы:
|
||||
- `api_method`
|
||||
- `logic_block`
|
||||
- `architecture_overview`
|
||||
- `domain_entity`
|
||||
- `ui_page`
|
||||
- `integration_doc`
|
||||
- `index_page`
|
||||
- `glossary_item`
|
||||
@@ -1,67 +0,0 @@
|
||||
# Frontmatter Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл описывает единый контракт YAML frontmatter для всех документов.
|
||||
|
||||
## Обязательные поля
|
||||
|
||||
```yaml
|
||||
id: string
|
||||
title: string
|
||||
doc_type: string
|
||||
domain: string
|
||||
sub_domain: string
|
||||
related_docs: []
|
||||
status: string
|
||||
```
|
||||
|
||||
## Поля совместимости и рекомендуемые поля
|
||||
|
||||
```yaml
|
||||
type: string
|
||||
name: string
|
||||
module: string
|
||||
layer: string
|
||||
updated_at: YYYY-MM-DD
|
||||
tags: []
|
||||
entities: []
|
||||
parent: string | null
|
||||
children: []
|
||||
links: {}
|
||||
source_of_truth: string
|
||||
related_code: []
|
||||
system_analytics_refs: []
|
||||
```
|
||||
|
||||
## Правила
|
||||
|
||||
- `id` должен быть стабильным и уникальным в пределах документации проекта.
|
||||
- `title` — человекочитаемый заголовок.
|
||||
- `doc_type` — канонический тип документа.
|
||||
- `domain` и `sub_domain` определяют бизнес-контекст документа.
|
||||
- `related_docs` хранит явные связи с другими markdown-документами.
|
||||
- `status` хранит жизненный цикл документа: например `draft`, `approved`, `active`.
|
||||
- `type` допустимо дублировать как alias для tooling-совместимости с индексаторами.
|
||||
- `name` — короткое системное имя документа.
|
||||
- `module` — модуль или подсистема.
|
||||
- `layer` — слой системы.
|
||||
- `updated_at` хранится в формате `YYYY-MM-DD`.
|
||||
|
||||
## Связи и навигация
|
||||
|
||||
- `entities` описывает сущности, связанные с документом.
|
||||
- `parent` и `children` описывают иерархию.
|
||||
- `links` описывает typed graph связей между документами, кодом и интеграциями.
|
||||
|
||||
## Формат links
|
||||
|
||||
```yaml
|
||||
links:
|
||||
called_by:
|
||||
- ext.health_probe
|
||||
uses_logic:
|
||||
- logic.some_flow
|
||||
integrates_with:
|
||||
- ext.some_system
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
# Linking Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл описывает, как связывать документы между собой.
|
||||
|
||||
## Иерархия
|
||||
|
||||
- `parent` используется для родительского документа.
|
||||
- `children` используется для прямых дочерних документов.
|
||||
- Иерархия должна быть осмысленной и стабильной.
|
||||
- Для общей точки входа допустим `index_page`.
|
||||
|
||||
## Графовые связи
|
||||
|
||||
Для `related_docs` используются ссылки на соседние документы.
|
||||
|
||||
Для `links` рекомендуется использовать typed-ключи:
|
||||
- `called_by`
|
||||
- `uses_logic`
|
||||
- `reads_db`
|
||||
- `writes_db`
|
||||
- `integrates_with`
|
||||
- `used_by`
|
||||
- `exposes_api`
|
||||
- `uses_entities`
|
||||
|
||||
## Правила использования
|
||||
|
||||
- Если документ логически входит в другой, использовать `parent`/`children`.
|
||||
- Если связь нужна для навигации между равноправными документами, дублировать ее в `related_docs`.
|
||||
- Если связь отражает поведение, интеграции или переиспользование, фиксировать ее в `links`.
|
||||
- Детальное описание интеграций хранить в body документа, а не только во frontmatter.
|
||||
@@ -1,24 +0,0 @@
|
||||
# Naming Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл описывает правила именования документов, файлов и идентификаторов.
|
||||
|
||||
## Правила для файлов
|
||||
|
||||
- Имена файлов должны быть в kebab-case.
|
||||
- Имя файла должно отражать одну тему.
|
||||
- Для шаблонов использовать суффикс `.template.md`.
|
||||
|
||||
## Правила для id
|
||||
|
||||
- `id` строится в формате `<type-group>.<name>`.
|
||||
- Примеры:
|
||||
- `api.send_message_endpoint`
|
||||
- `logic.telegram_notification_loop`
|
||||
- `architecture.telegram_notify_app`
|
||||
|
||||
## Правила для title
|
||||
|
||||
- `title` должен быть кратким и человекочитаемым.
|
||||
- В `title` допускаются пробелы и естественный язык.
|
||||
@@ -1,19 +0,0 @@
|
||||
# Writing Style
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает правила стиля для текстового наполнения документации.
|
||||
|
||||
## Правила стиля
|
||||
|
||||
- Текст должен быть лаконичным.
|
||||
- Формулировки должны быть точными и техническими.
|
||||
- Summary должен быть кратким explain-слоем.
|
||||
- Details должен раскрывать суть без лишней воды.
|
||||
- Нежелательно смешивать несколько тем в одном документе.
|
||||
- Если детали относятся к другому артефакту, их нужно выносить в отдельный документ.
|
||||
|
||||
## Язык
|
||||
|
||||
- Основной язык документации — русский.
|
||||
- Технические термины, названия классов, API, RAG, OpenAPI, runtime и другие устоявшиеся identifiers можно оставлять на английском.
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
id: api.example_method
|
||||
type: api_method
|
||||
doc_type: api_method
|
||||
name: example_method
|
||||
title: HTTP API /example
|
||||
module: example_module
|
||||
layer: application
|
||||
domain: example_domain
|
||||
sub_domain: example_subdomain
|
||||
related_docs: []
|
||||
status: draft
|
||||
updated_at: 2026-03-20
|
||||
source_of_truth: code
|
||||
parent: null
|
||||
children: []
|
||||
tags: []
|
||||
entities: []
|
||||
links: {}
|
||||
---
|
||||
|
||||
# HTTP API /example
|
||||
|
||||
## Summary
|
||||
|
||||
Краткое описание метода.
|
||||
|
||||
## Details
|
||||
|
||||
## Описание
|
||||
|
||||
Короткое описание сути метода.
|
||||
|
||||
## Сценарий
|
||||
|
||||
**Название:**
|
||||
|
||||
**Предусловия:**
|
||||
-
|
||||
|
||||
**Триггер:**
|
||||
-
|
||||
|
||||
**Основной сценарий:**
|
||||
1.
|
||||
|
||||
**Альтернативный сценарий:**
|
||||
1.
|
||||
|
||||
**Обработка ошибок:**
|
||||
1.
|
||||
|
||||
**Постусловие:**
|
||||
-
|
||||
|
||||
## Функциональные требования
|
||||
|
||||
**FR-1.**
|
||||
|
||||
## Нефункциональные требования
|
||||
|
||||
**NFR-1.**
|
||||
|
||||
## Контракт
|
||||
|
||||
### Входные параметры
|
||||
|
||||
| Параметр | Где передается | Тип | Обязательность | Ограничения | Описание | Пример |
|
||||
|---|---|---|---|---|---|---|
|
||||
| | | | | | | |
|
||||
|
||||
### Выходные параметры
|
||||
|
||||
| Поле | Тип | Обязательность | Ограничения | Описание | Заполнение | Пример |
|
||||
|---|---|---|---|---|---|---|
|
||||
| | | | | | | |
|
||||
|
||||
### Интеграции
|
||||
|
||||
### Ошибки
|
||||
|
||||
### Связанный код
|
||||
|
||||
### История изменений
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
---
|
||||
id: architecture.example_system
|
||||
type: architecture_overview
|
||||
doc_type: architecture_overview
|
||||
name: example_system
|
||||
title: Обзор архитектуры Example System
|
||||
module: example_module
|
||||
layer: system
|
||||
domain: example_domain
|
||||
sub_domain: example_subdomain
|
||||
related_docs: []
|
||||
status: draft
|
||||
updated_at: 2026-03-20
|
||||
source_of_truth: mixed
|
||||
parent: null
|
||||
children: []
|
||||
tags: []
|
||||
entities: []
|
||||
links: {}
|
||||
---
|
||||
|
||||
# Обзор архитектуры Example System
|
||||
|
||||
## Summary
|
||||
|
||||
Краткое описание архитектуры.
|
||||
|
||||
## Details
|
||||
|
||||
### Описание
|
||||
|
||||
### Контекст
|
||||
|
||||
### Границы системы
|
||||
|
||||
### Компоненты
|
||||
|
||||
### Интеграционные сценарии
|
||||
|
||||
### Интеграции
|
||||
|
||||
### Ограничения
|
||||
|
||||
### Связанный код
|
||||
|
||||
### Связанные документы
|
||||
|
||||
### История изменений
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
id: domain.example_entity
|
||||
type: domain_entity
|
||||
doc_type: domain_entity
|
||||
name: example_entity
|
||||
title: Пример доменной сущности
|
||||
module: example_module
|
||||
layer: domain
|
||||
domain: example_domain
|
||||
sub_domain: example_subdomain
|
||||
related_docs: []
|
||||
status: draft
|
||||
updated_at: 2026-03-20
|
||||
source_of_truth: code
|
||||
parent: null
|
||||
children: []
|
||||
tags: []
|
||||
entities: []
|
||||
links: {}
|
||||
---
|
||||
|
||||
# Пример доменной сущности
|
||||
|
||||
## Summary
|
||||
|
||||
Краткое описание сущности.
|
||||
|
||||
## Details
|
||||
|
||||
### Описание
|
||||
|
||||
### Модель данных
|
||||
|
||||
### Состояния и инварианты
|
||||
|
||||
### Технический use case
|
||||
|
||||
### Функциональные требования
|
||||
|
||||
### Нефункциональные требования
|
||||
|
||||
### Интеграции
|
||||
|
||||
### Связанный код
|
||||
|
||||
### Связанные документы
|
||||
|
||||
### История изменений
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
id: logic.example_block
|
||||
type: logic_block
|
||||
doc_type: logic_block
|
||||
name: example_block
|
||||
title: Пример блока логики
|
||||
module: example_module
|
||||
layer: application
|
||||
domain: example_domain
|
||||
sub_domain: example_subdomain
|
||||
related_docs: []
|
||||
status: draft
|
||||
updated_at: 2026-03-20
|
||||
source_of_truth: code
|
||||
parent: null
|
||||
children: []
|
||||
tags: []
|
||||
entities: []
|
||||
links: {}
|
||||
---
|
||||
|
||||
# Пример блока логики
|
||||
|
||||
## Summary
|
||||
|
||||
Краткое описание блока логики.
|
||||
|
||||
## Details
|
||||
|
||||
### Описание
|
||||
|
||||
### Контекст
|
||||
|
||||
### Технический use case
|
||||
|
||||
### Функциональные требования
|
||||
|
||||
### Нефункциональные требования
|
||||
|
||||
### Интеграции
|
||||
|
||||
### Ограничения и условия вызова
|
||||
|
||||
### Ошибки и деградации
|
||||
|
||||
### Связанные API
|
||||
|
||||
### Связанный код
|
||||
|
||||
### История изменений
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
id: ui.example_page
|
||||
type: ui_page
|
||||
doc_type: ui_page
|
||||
name: example_page
|
||||
title: Пример UI-страницы
|
||||
module: example_module
|
||||
layer: presentation
|
||||
domain: example_domain
|
||||
sub_domain: example_subdomain
|
||||
related_docs: []
|
||||
status: draft
|
||||
updated_at: 2026-03-20
|
||||
source_of_truth: mixed
|
||||
parent: null
|
||||
children: []
|
||||
tags: []
|
||||
entities: []
|
||||
links: {}
|
||||
---
|
||||
|
||||
# Пример UI-страницы
|
||||
|
||||
## Summary
|
||||
|
||||
Краткое описание страницы и её назначения.
|
||||
|
||||
## Details
|
||||
|
||||
### Назначение страницы
|
||||
|
||||
### Пользовательский сценарий
|
||||
|
||||
### Основные блоки интерфейса
|
||||
|
||||
### Связанные API и сущности
|
||||
|
||||
### Функциональные требования
|
||||
|
||||
### Нефункциональные требования
|
||||
|
||||
### Ограничения и граничные случаи
|
||||
|
||||
### Ошибки и валидации
|
||||
|
||||
### Связанный код
|
||||
|
||||
### Связанные документы
|
||||
|
||||
### История изменений
|
||||
@@ -1,39 +0,0 @@
|
||||
# API Method Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает правила для документов типа `api_method`.
|
||||
|
||||
## Когда использовать
|
||||
|
||||
Использовать для описания одного HTTP endpoint или одного отдельного API метода.
|
||||
|
||||
## Обязательная структура
|
||||
|
||||
Документ должен содержать:
|
||||
- YAML frontmatter
|
||||
- `# <title>`
|
||||
- `## Summary`
|
||||
- `## Details`
|
||||
|
||||
Внутри `## Details` обязательны:
|
||||
- `### Описание`
|
||||
- `### Сценарий`
|
||||
- `### Функциональные требования`
|
||||
- `### Нефункциональные требования`
|
||||
- `### Контракт`
|
||||
|
||||
## Особые правила
|
||||
|
||||
- Сценарий оформляется как технический use case.
|
||||
- Функциональные требования маркируются `FR-*`.
|
||||
- Нефункциональные требования маркируются `NFR-*`.
|
||||
- Контракт должен быть пригоден для последующей сборки OpenAPI.
|
||||
- Если у метода есть интеграции, они выносятся в `### Интеграции`.
|
||||
- Ошибки и HTTP-коды либо описываются в `### Ошибки`, либо ссылаются на централизованный каталог ошибок.
|
||||
|
||||
## Ошибки оформления
|
||||
|
||||
- Нельзя заменять контракт общим текстовым описанием.
|
||||
- Нельзя смешивать несколько endpoint в одном документе.
|
||||
- Нельзя хранить связи и навигацию вне frontmatter.
|
||||
@@ -1,31 +0,0 @@
|
||||
# Architecture Overview Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает правила для документов типа `architecture_overview`.
|
||||
|
||||
## Когда использовать
|
||||
|
||||
Использовать как входной документ для понимания системы, модуля или сервиса.
|
||||
|
||||
## Обязательная структура
|
||||
|
||||
Документ должен содержать:
|
||||
- YAML frontmatter
|
||||
- `# <title>`
|
||||
- `## Summary`
|
||||
- `## Details`
|
||||
|
||||
## Что описывать в Details
|
||||
|
||||
- границы системы
|
||||
- основные компоненты
|
||||
- ключевые взаимодействия
|
||||
- интеграционные сценарии
|
||||
- главные ограничения
|
||||
- ссылки на дочерние документы по API, logic, domain и другим артефактам
|
||||
|
||||
## Ошибки оформления
|
||||
|
||||
- Нельзя дублировать в архитектурном обзоре полные API-контракты.
|
||||
- Нельзя делать архитектурный обзор единственным документом на всю систему без декомпозиции.
|
||||
@@ -1,30 +0,0 @@
|
||||
# Domain Entity Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает правила для документов типа `domain_entity`.
|
||||
|
||||
## Когда использовать
|
||||
|
||||
Использовать для описания одной доменной сущности, ее смысла, состояния и роли в системе.
|
||||
|
||||
## Обязательная структура
|
||||
|
||||
Документ должен содержать:
|
||||
- YAML frontmatter
|
||||
- `# <title>`
|
||||
- `## Summary`
|
||||
- `## Details`
|
||||
|
||||
## Что описывать в Details
|
||||
|
||||
- смысл сущности
|
||||
- ключевые атрибуты
|
||||
- состояния или инварианты
|
||||
- использование сущности в системе
|
||||
- интеграции с API, workflow или внешними потребителями, если они важны для понимания модели
|
||||
|
||||
## Ошибки оформления
|
||||
|
||||
- Нельзя смешивать несколько независимых сущностей в одном документе.
|
||||
- Нельзя подменять доменную сущность описанием endpoint или workflow.
|
||||
@@ -1,25 +0,0 @@
|
||||
# Integration Doc Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает правила для документов типа `integration_doc`.
|
||||
|
||||
## Когда использовать
|
||||
|
||||
Использовать для описания интеграции между системами, сервисами или внешними провайдерами.
|
||||
|
||||
## Обязательная структура
|
||||
|
||||
Документ должен содержать:
|
||||
- YAML frontmatter
|
||||
- `# <title>`
|
||||
- `## Summary`
|
||||
- `## Details`
|
||||
|
||||
## Что описывать в Details
|
||||
|
||||
- цель интеграции
|
||||
- участвующие стороны
|
||||
- направление обмена
|
||||
- ключевой сценарий взаимодействия
|
||||
- ограничения и риски
|
||||
@@ -1,31 +0,0 @@
|
||||
# Logic Block Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает правила для документов типа `logic_block`.
|
||||
|
||||
## Когда использовать
|
||||
|
||||
Использовать для описания одного законченного блока логики, workflow или процесса.
|
||||
|
||||
## Обязательная структура
|
||||
|
||||
Документ должен содержать:
|
||||
- YAML frontmatter
|
||||
- `# <title>`
|
||||
- `## Summary`
|
||||
- `## Details`
|
||||
|
||||
## Что описывать в Details
|
||||
|
||||
- назначение логического блока
|
||||
- входы и выходы
|
||||
- последовательность выполнения
|
||||
- интеграции
|
||||
- ключевые ограничения
|
||||
- состояние и ошибки, если они важны для понимания блока
|
||||
|
||||
## Ошибки оформления
|
||||
|
||||
- Нельзя описывать весь модуль целиком, если логика распадается на несколько независимых блоков.
|
||||
- Нельзя превращать документ в пересказ исходного кода построчно.
|
||||
@@ -1,24 +0,0 @@
|
||||
# UI Page Rules
|
||||
|
||||
## Назначение
|
||||
|
||||
Этот файл задает правила для документов типа `ui_page`.
|
||||
|
||||
## Когда использовать
|
||||
|
||||
Использовать для описания одной пользовательской страницы, экрана или отдельного UI-сценария.
|
||||
|
||||
## Обязательная структура
|
||||
|
||||
Документ должен содержать:
|
||||
- YAML frontmatter
|
||||
- `# <title>`
|
||||
- `## Summary`
|
||||
- `## Details`
|
||||
|
||||
## Что описывать в Details
|
||||
|
||||
- назначение страницы
|
||||
- пользовательский сценарий
|
||||
- основные блоки интерфейса
|
||||
- связанные API и сущности
|
||||
@@ -0,0 +1,9 @@
|
||||
# DOC_UPDATE/FROM_FEATURE v2 Rules
|
||||
|
||||
Этот каталог содержит общие rules для всех шагов и подпроцессов workflow `doc_update_from_feature_v2`.
|
||||
|
||||
- `attribute_resolution.md` — правила определения type/id/application/platform.
|
||||
- `path_resolution.md` — правила резолва путей документации.
|
||||
- `section_frontmatter.md` — инструкции для frontmatter.
|
||||
- `section_summary.md` — инструкции для summary.
|
||||
- `section_details.md` — инструкции для details.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# Attribute Resolution
|
||||
|
||||
1. Приоритет: теги из requirement > metadata документа > LLM fallback.
|
||||
2. Обязательные атрибуты: `type`, `id`, `application`, `platform`.
|
||||
3. Если атрибут отсутствует, разрешен fallback через LLM.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Path Resolution
|
||||
|
||||
Путь строится как:
|
||||
|
||||
`docs/<application>/<platform>/<type>/<id>.md`
|
||||
|
||||
Нормализация сегментов: lowercase + замена недопустимых символов на `-`.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Details Rules
|
||||
|
||||
Details содержит детализированное описание поведения, ограничений и сценариев.
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
# Frontmatter Rules
|
||||
|
||||
1. Frontmatter всегда в блоке `---`.
|
||||
2. Должны быть поля id/title/type/application/platform.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Summary Rules
|
||||
|
||||
Summary содержит краткую цель страницы и основные изменения.
|
||||
@@ -18,6 +18,10 @@ from app.core.agent.processes.v2.workflows.doc_update_from_feature.graph import
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureContext,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.graph import DocUpdateFromFeatureV2Graph
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.general_qa_summary.workflow_runtime.context import GeneralQaSummaryContext
|
||||
from app.core.agent.processes.v2.workflows.general_qa_summary.graph import GeneralQaSummaryGraph
|
||||
from app.core.agent.utils.llm import AgentLlmService
|
||||
@@ -44,6 +48,7 @@ class V2Process(AgentProcess):
|
||||
general_summary_prompt_name: str = "v2_general.summary_answer",
|
||||
workflow_llm_enabled: bool = True,
|
||||
doc_rules_enabled: bool = True,
|
||||
doc_update_workflow_version: str = "v2",
|
||||
) -> None:
|
||||
self._router = router or V2IntentRouter()
|
||||
gate = evidence_gate or DocsEvidenceGate()
|
||||
@@ -51,6 +56,8 @@ class V2Process(AgentProcess):
|
||||
self._general_summary_prompt_name = general_summary_prompt_name
|
||||
self._workflow_llm_enabled = workflow_llm_enabled
|
||||
self._doc_rules_enabled = doc_rules_enabled
|
||||
self._doc_update_workflow_version = doc_update_workflow_version.strip().lower() or "v2"
|
||||
doc_update_graph = self._build_doc_update_workflow(llm, doc_rules_enabled)
|
||||
self._workflows: dict[tuple[str, str, str], Any] = {
|
||||
(V2Domain.DOCS, V2Intent.DOC_EXPLAIN, V2Subintent.SUMMARY): DocExplainSummaryGraph(
|
||||
llm,
|
||||
@@ -69,10 +76,7 @@ class V2Process(AgentProcess):
|
||||
policy_resolver=policy_resolver,
|
||||
rag_adapter=rag_adapter,
|
||||
),
|
||||
(V2Domain.DOCS, V2Intent.DOC_UPDATE, V2Subintent.FROM_FEATURE): DocUpdateFromFeatureGraph(
|
||||
llm=llm,
|
||||
doc_rules_enabled=doc_rules_enabled,
|
||||
),
|
||||
(V2Domain.DOCS, V2Intent.DOC_UPDATE, V2Subintent.FROM_FEATURE): doc_update_graph,
|
||||
(V2Domain.GENERAL, V2Intent.GENERAL_QA, V2Subintent.SUMMARY): GeneralQaSummaryGraph(
|
||||
llm,
|
||||
policy_resolver=policy_resolver,
|
||||
@@ -175,12 +179,20 @@ class V2Process(AgentProcess):
|
||||
)
|
||||
)
|
||||
if route.intent == V2Intent.DOC_UPDATE and route.subintent == V2Subintent.FROM_FEATURE:
|
||||
if self._doc_update_workflow_version == "legacy":
|
||||
return await workflow.run(
|
||||
DocUpdateFromFeatureContext(
|
||||
runtime=runtime_context,
|
||||
route=route,
|
||||
rag_session_id=rag_session_id,
|
||||
doc_rules_enabled=self._doc_rules_enabled,
|
||||
)
|
||||
)
|
||||
return await workflow.run(
|
||||
DocUpdateFromFeatureContext(
|
||||
DocUpdateFromFeatureV2Context(
|
||||
runtime=runtime_context,
|
||||
route=route,
|
||||
rag_session_id=rag_session_id,
|
||||
doc_rules_enabled=self._doc_rules_enabled,
|
||||
)
|
||||
)
|
||||
return await workflow.run(
|
||||
@@ -192,3 +204,8 @@ class V2Process(AgentProcess):
|
||||
workflow_llm_enabled=self._workflow_llm_enabled,
|
||||
)
|
||||
)
|
||||
|
||||
def _build_doc_update_workflow(self, llm: AgentLlmService, doc_rules_enabled: bool):
|
||||
if self._doc_update_workflow_version == "legacy":
|
||||
return DocUpdateFromFeatureGraph(llm=llm, doc_rules_enabled=doc_rules_enabled)
|
||||
return DocUpdateFromFeatureV2Graph(llm=llm)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
"""DOC_UPDATE/FROM_FEATURE v2 workflow package."""
|
||||
|
||||
__all__ = ["DocUpdateFromFeatureV2Graph"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
if name == "DocUpdateFromFeatureV2Graph":
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.graph import DocUpdateFromFeatureV2Graph
|
||||
|
||||
return DocUpdateFromFeatureV2Graph
|
||||
raise AttributeError(name)
|
||||
@@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step1_resolve_source.step import (
|
||||
ResolveSourceStep,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step2_load_source_content.step import (
|
||||
LoadSourceContentStep,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step3_parse_requirements.step import (
|
||||
ParseRequirementsStep,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step4_prepare_tasks.step import (
|
||||
PrepareRequirementTasksStep,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step4_load_rules.step import LoadRulesStep
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step5_execute_subprocesses.step import (
|
||||
ExecuteRequirementSubprocessesStep,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step6_finalize.step import FinalizeAnswerStep
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.buffered_graph import (
|
||||
DocUpdateFromFeatureV2WorkflowGraph,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.utils.llm import AgentLlmService
|
||||
|
||||
|
||||
class DocUpdateFromFeatureV2Graph(DocUpdateFromFeatureV2WorkflowGraph[DocUpdateFromFeatureV2Context]):
|
||||
def __init__(self, llm: AgentLlmService) -> None:
|
||||
super().__init__(
|
||||
workflow_id="v2.docs_update.from_feature_v2",
|
||||
source="workflow.v2.docs_update.from_feature_v2",
|
||||
steps=[
|
||||
ResolveSourceStep(),
|
||||
LoadSourceContentStep(),
|
||||
ParseRequirementsStep(),
|
||||
PrepareRequirementTasksStep(llm=llm),
|
||||
LoadRulesStep(),
|
||||
ExecuteRequirementSubprocessesStep(llm=llm),
|
||||
FinalizeAnswerStep(),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
"""Auto-generated package init."""
|
||||
+1
@@ -0,0 +1 @@
|
||||
"""Auto-generated package init."""
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.utils.workflow import WorkflowStep
|
||||
|
||||
|
||||
class ResolveSourceStep(WorkflowStep[DocUpdateFromFeatureV2Context]):
|
||||
step_id = "step1_resolve_source"
|
||||
title = "Определение источника аналитики"
|
||||
|
||||
_PATH_PATTERN = re.compile(r"(/[^\n`]+?\.md)")
|
||||
_URL_PATTERN = re.compile(r"https?://[^\s)]+")
|
||||
|
||||
async def run(self, context: DocUpdateFromFeatureV2Context) -> DocUpdateFromFeatureV2Context:
|
||||
if context.answer:
|
||||
return context
|
||||
query = context.route.user_query
|
||||
path = self._extract_path(query)
|
||||
if path:
|
||||
context.source_ref = path
|
||||
context.source_kind = "markdown_file"
|
||||
return context
|
||||
url = self._extract_url(query)
|
||||
if url:
|
||||
context.source_ref = url
|
||||
context.source_kind = "confluence_url" if "confluence" in url.lower() else "url"
|
||||
return context
|
||||
context.issues.append("Не удалось определить источник системной аналитики (ожидался путь .md или URL).")
|
||||
return context
|
||||
|
||||
def _extract_path(self, query: str) -> str:
|
||||
if "`" in query:
|
||||
for chunk in query.split("`"):
|
||||
value = chunk.strip().strip('"').strip("'")
|
||||
if value.endswith(".md") and value.startswith("/"):
|
||||
return value
|
||||
match = self._PATH_PATTERN.search(query)
|
||||
return match.group(1).strip().strip('"').strip("'") if match else ""
|
||||
|
||||
def _extract_url(self, query: str) -> str:
|
||||
match = self._URL_PATTERN.search(query)
|
||||
return match.group(0).strip() if match else ""
|
||||
|
||||
def trace_input(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
query = str(context.route.user_query or "")
|
||||
return {"query_excerpt": query[:300], "query_len": len(query)}
|
||||
|
||||
def trace_output(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {"source_kind": context.source_kind, "source_ref": context.source_ref}
|
||||
+1
@@ -0,0 +1 @@
|
||||
"""Auto-generated package init."""
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.utils.workflow import WorkflowStep
|
||||
|
||||
|
||||
class LoadSourceContentStep(WorkflowStep[DocUpdateFromFeatureV2Context]):
|
||||
step_id = "step2_load_source_content"
|
||||
title = "Загрузка системной аналитики"
|
||||
|
||||
async def run(self, context: DocUpdateFromFeatureV2Context) -> DocUpdateFromFeatureV2Context:
|
||||
if context.answer:
|
||||
return context
|
||||
if not context.source_ref:
|
||||
return context
|
||||
if context.source_kind == "confluence_url":
|
||||
context.issues.append("Confluence source пока не реализован (stub).")
|
||||
return context
|
||||
if context.source_kind != "markdown_file":
|
||||
context.issues.append("Поддерживается только markdown-файл (.md).")
|
||||
return context
|
||||
|
||||
source_path = Path(context.source_ref)
|
||||
if not source_path.exists() or not source_path.is_file():
|
||||
context.issues.append(f"Файл системной аналитики не найден: {context.source_ref}")
|
||||
return context
|
||||
try:
|
||||
context.source_content = source_path.read_text(encoding="utf-8")
|
||||
context.project_root = self._resolve_project_root(source_path).as_posix()
|
||||
except Exception as exc:
|
||||
context.issues.append(f"Не удалось прочитать системную аналитику: {exc}")
|
||||
return context
|
||||
|
||||
def _resolve_project_root(self, source_path: Path) -> Path:
|
||||
parts = list(source_path.parts)
|
||||
if "_incoming" in parts:
|
||||
idx = parts.index("_incoming")
|
||||
if idx > 0:
|
||||
return Path(*parts[:idx])
|
||||
return source_path.parent
|
||||
|
||||
def trace_input(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {"source_kind": context.source_kind, "source_ref": context.source_ref}
|
||||
|
||||
def trace_output(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {
|
||||
"project_root": context.project_root,
|
||||
"source_content_len": len(context.source_content or ""),
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
"""Auto-generated package init."""
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import (
|
||||
AnalyticsMeta,
|
||||
RequirementUnit,
|
||||
)
|
||||
|
||||
|
||||
class MarkdownHeadingScanner:
|
||||
_HEADING_RE = re.compile(r"^(#{1,6})\s+(.+?)\s*$")
|
||||
_ROOT_RE = re.compile(r"^(?:6(?:[.)\s]|$)|функциональные требования\b|описание изменений\b)", re.IGNORECASE)
|
||||
_SECTION_RE = re.compile(r"^6(?:\.\d+)+(?:\s|$|[.)])")
|
||||
|
||||
def find_root(self, lines: list[str]) -> tuple[int, int, int] | None:
|
||||
headings = list(self.iter_headings(lines))
|
||||
for idx, (line_index, level, title) in enumerate(headings):
|
||||
if not self._ROOT_RE.match(title.strip()):
|
||||
continue
|
||||
end = len(lines)
|
||||
for next_line, next_level, _ in headings[idx + 1 :]:
|
||||
if next_level <= level:
|
||||
end = next_line
|
||||
break
|
||||
return line_index, level, end
|
||||
return None
|
||||
|
||||
def split_sections(self, lines: list[str], *, section_level: int) -> tuple[list[str], list[tuple[str, list[str]]]]:
|
||||
meta_lines: list[str] = []
|
||||
sections: list[tuple[str, list[str]]] = []
|
||||
current_title = ""
|
||||
current_body: list[str] = []
|
||||
seen_section = False
|
||||
|
||||
for raw in lines:
|
||||
heading = self._match_heading(raw)
|
||||
if heading and heading[0] == section_level and self._SECTION_RE.match(heading[1].strip()):
|
||||
if current_title:
|
||||
sections.append((current_title, current_body))
|
||||
current_title = heading[1].strip()
|
||||
current_body = []
|
||||
seen_section = True
|
||||
continue
|
||||
if current_title:
|
||||
current_body.append(raw)
|
||||
elif not seen_section:
|
||||
meta_lines.append(raw)
|
||||
|
||||
if current_title:
|
||||
sections.append((current_title, current_body))
|
||||
return meta_lines, sections
|
||||
|
||||
def iter_headings(self, lines: list[str]) -> list[tuple[int, int, str]]:
|
||||
items: list[tuple[int, int, str]] = []
|
||||
for index, raw in enumerate(lines):
|
||||
heading = self._match_heading(raw)
|
||||
if heading:
|
||||
items.append((index, heading[0], heading[1]))
|
||||
return items
|
||||
|
||||
def _match_heading(self, raw: str) -> tuple[int, str] | None:
|
||||
match = self._HEADING_RE.match(raw.strip())
|
||||
if not match:
|
||||
return None
|
||||
return len(match.group(1)), match.group(2).strip()
|
||||
|
||||
|
||||
class MarkdownMetadataParser:
|
||||
_KEY_VALUE_RE = re.compile(r"^[-*]?\s*`?([A-Za-z_][A-Za-z0-9_-]*)`?\s*:\s*(.*)$")
|
||||
|
||||
def parse_block(self, lines: list[str]) -> dict[str, str]:
|
||||
values: dict[str, str] = {}
|
||||
for raw in lines:
|
||||
line = raw.strip()
|
||||
if not line:
|
||||
continue
|
||||
match = self._KEY_VALUE_RE.match(line)
|
||||
if not match:
|
||||
break
|
||||
key = match.group(1).strip().lower()
|
||||
value = self._normalize_value(match.group(2))
|
||||
values[key] = value
|
||||
return values
|
||||
|
||||
def split_metadata_and_body(self, lines: list[str]) -> tuple[dict[str, str], list[str]]:
|
||||
metadata: dict[str, str] = {}
|
||||
body_index = 0
|
||||
for idx, raw in enumerate(lines):
|
||||
line = raw.strip()
|
||||
if not line:
|
||||
body_index = idx + 1
|
||||
continue
|
||||
match = self._KEY_VALUE_RE.match(line)
|
||||
if not match:
|
||||
break
|
||||
key = match.group(1).strip().lower()
|
||||
value = self._normalize_value(match.group(2))
|
||||
metadata[key] = value
|
||||
body_index = idx + 1
|
||||
return metadata, lines[body_index:]
|
||||
|
||||
def _normalize_value(self, value: str) -> str:
|
||||
return value.strip().strip("`").strip('"').strip("'").strip()
|
||||
|
||||
|
||||
class FunctionalRequirementsParser:
|
||||
_SECTION_KEY_RE = re.compile(r"^(6(?:\.\d+)+)[.)\s-]*(.*)$")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
scanner: MarkdownHeadingScanner | None = None,
|
||||
metadata_parser: MarkdownMetadataParser | None = None,
|
||||
) -> None:
|
||||
self._scanner = scanner or MarkdownHeadingScanner()
|
||||
self._metadata_parser = metadata_parser or MarkdownMetadataParser()
|
||||
|
||||
def parse(self, content: str) -> tuple[AnalyticsMeta, list[RequirementUnit]]:
|
||||
lines = content.splitlines()
|
||||
meta = self._parse_preamble_meta(lines)
|
||||
root = self._scanner.find_root(lines)
|
||||
if root is None:
|
||||
return meta, []
|
||||
|
||||
start, level, end = root
|
||||
root_meta_lines, sections = self._scanner.split_sections(lines[start + 1 : end], section_level=level + 1)
|
||||
root_meta = self._metadata_parser.parse_block(root_meta_lines)
|
||||
meta.domain = root_meta.get("domain", meta.domain)
|
||||
meta.subdomain = root_meta.get("sub_domain", root_meta.get("subdomain", meta.subdomain))
|
||||
meta.application = root_meta.get("application", meta.application)
|
||||
meta.platform = root_meta.get("platform", meta.platform)
|
||||
|
||||
units: list[RequirementUnit] = []
|
||||
for raw_heading, body_lines in sections:
|
||||
metadata, body = self._metadata_parser.split_metadata_and_body(body_lines)
|
||||
section_key, heading = self._split_heading(raw_heading)
|
||||
units.append(
|
||||
RequirementUnit(
|
||||
section_key=section_key,
|
||||
heading=heading,
|
||||
body="\n".join(body).strip(),
|
||||
metadata=metadata,
|
||||
)
|
||||
)
|
||||
return meta, units
|
||||
|
||||
def _parse_preamble_meta(self, lines: list[str]) -> AnalyticsMeta:
|
||||
preamble: list[str] = []
|
||||
for raw in lines:
|
||||
if raw.strip().startswith("#"):
|
||||
break
|
||||
preamble.append(raw)
|
||||
values = self._metadata_parser.parse_block(preamble)
|
||||
return AnalyticsMeta(
|
||||
analysis_id=values.get("analysis_id", ""),
|
||||
application=values.get("application", ""),
|
||||
platform=values.get("platform", ""),
|
||||
domain=values.get("domain", ""),
|
||||
subdomain=values.get("sub_domain", values.get("subdomain", "")),
|
||||
)
|
||||
|
||||
def _split_heading(self, raw_heading: str) -> tuple[str, str]:
|
||||
match = self._SECTION_KEY_RE.match(raw_heading.strip())
|
||||
if not match:
|
||||
return "", raw_heading.strip()
|
||||
section_key = match.group(1).strip()
|
||||
heading = match.group(2).strip() or section_key
|
||||
return section_key, heading
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step3_parse_requirements.parser import (
|
||||
FunctionalRequirementsParser,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.utils.workflow import WorkflowStep
|
||||
|
||||
|
||||
class ParseRequirementsStep(WorkflowStep[DocUpdateFromFeatureV2Context]):
|
||||
step_id = "step3_parse_requirements"
|
||||
title = "Парсинг раздела функциональных требований"
|
||||
|
||||
def __init__(self, parser: FunctionalRequirementsParser | None = None) -> None:
|
||||
self._parser = parser or FunctionalRequirementsParser()
|
||||
|
||||
async def run(self, context: DocUpdateFromFeatureV2Context) -> DocUpdateFromFeatureV2Context:
|
||||
if context.answer or not context.source_content:
|
||||
return context
|
||||
meta, units = self._parser.parse(context.source_content)
|
||||
context.analytics_meta = meta
|
||||
context.requirements = units
|
||||
|
||||
if not units:
|
||||
context.issues.append(
|
||||
"Не найден раздел 6 с подразделами 6.x для генерации страниц документации."
|
||||
)
|
||||
if not context.analytics_meta.analysis_id:
|
||||
context.issues.append("Не найден metadata-тег analysis_id в начале аналитики.")
|
||||
return context
|
||||
|
||||
def trace_input(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {"source_content_len": len(context.source_content or "")}
|
||||
|
||||
def trace_output(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {
|
||||
"analytics_meta": {
|
||||
"analysis_id": context.analytics_meta.analysis_id,
|
||||
"application": context.analytics_meta.application,
|
||||
"platform": context.analytics_meta.platform,
|
||||
"domain": context.analytics_meta.domain,
|
||||
"sub_domain": context.analytics_meta.subdomain,
|
||||
},
|
||||
"requirements": [
|
||||
{
|
||||
"section_key": item.section_key,
|
||||
"heading": item.heading,
|
||||
"metadata": dict(item.metadata),
|
||||
}
|
||||
for item in context.requirements
|
||||
],
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
"""Auto-generated package init."""
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import RuleDocument
|
||||
from app.core.agent.utils.workflow import WorkflowStep
|
||||
|
||||
|
||||
class LoadRulesStep(WorkflowStep[DocUpdateFromFeatureV2Context]):
|
||||
step_id = "step5_load_rules"
|
||||
title = "Загрузка правил документации v3"
|
||||
|
||||
def __init__(self, rules_root: Path | None = None) -> None:
|
||||
self._rules_root = rules_root or self._discover_rules_root()
|
||||
|
||||
async def run(self, context: DocUpdateFromFeatureV2Context) -> DocUpdateFromFeatureV2Context:
|
||||
if context.answer:
|
||||
return context
|
||||
if not self._rules_root.exists():
|
||||
context.issues.append(f"Папка rules не найдена: {self._rules_root.as_posix()}")
|
||||
return context
|
||||
loaded: list[RuleDocument] = []
|
||||
for item in sorted(self._rules_root.rglob("*.md")):
|
||||
try:
|
||||
loaded.append(
|
||||
RuleDocument(
|
||||
name=item.relative_to(self._rules_root).as_posix(),
|
||||
content=item.read_text(encoding="utf-8"),
|
||||
)
|
||||
)
|
||||
except Exception as exc:
|
||||
context.issues.append(f"Не удалось прочитать rule {item.name}: {exc}")
|
||||
context.rules = loaded
|
||||
if not context.rules:
|
||||
context.issues.append("Rules v2 пустые: не найдено ни одного *.md файла.")
|
||||
return context
|
||||
|
||||
def _discover_rules_root(self) -> Path:
|
||||
current = Path(__file__).resolve()
|
||||
for parent in current.parents:
|
||||
candidate = parent / "_process" / "doc_rules_v3"
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
return current.parents[9] / "_process" / "doc_rules_v3"
|
||||
|
||||
def trace_input(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {"rules_root": self._rules_root.as_posix()}
|
||||
|
||||
def trace_output(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {
|
||||
"rules_count": len(context.rules),
|
||||
"rule_names": [item.name for item in context.rules],
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step4_prepare_tasks.step import (
|
||||
PrepareRequirementTasksStep,
|
||||
)
|
||||
|
||||
__all__ = ["PrepareRequirementTasksStep"]
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step5_execute_subprocesses.path_resolver import (
|
||||
DocsPathResolver,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step5_execute_subprocesses.services import (
|
||||
TaskActionResolver,
|
||||
TaskAttributeResolver,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import (
|
||||
RequirementTaskContext,
|
||||
)
|
||||
from app.core.agent.utils.llm import AgentLlmService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.core.rag.persistence.query_repository import RagQueryRepository
|
||||
|
||||
|
||||
class DocsCatalogLoader:
|
||||
def __init__(self, query_repository: "RagQueryRepository | None" = None) -> None:
|
||||
self._query_repository = query_repository
|
||||
|
||||
def load(self, context: DocUpdateFromFeatureV2Context) -> None:
|
||||
if not context.rag_session_id:
|
||||
context.issues.append("Отсутствует active RAG session: fallback по структуре документации будет ограничен.")
|
||||
return
|
||||
repository = self._query_repository or self._build_repository()
|
||||
try:
|
||||
context.docs_catalog_rows = repository.list_docs_scope_index_rows(context.rag_session_id)
|
||||
except Exception as exc:
|
||||
context.issues.append(f"Не удалось загрузить docs catalog rows: {exc}")
|
||||
|
||||
def _build_repository(self) -> "RagQueryRepository":
|
||||
from app.core.rag.persistence.query_repository import RagQueryRepository
|
||||
|
||||
self._query_repository = RagQueryRepository()
|
||||
return self._query_repository
|
||||
|
||||
|
||||
class RequirementTaskBuilder:
|
||||
def __init__(self, llm: AgentLlmService) -> None:
|
||||
self._path_resolver = DocsPathResolver()
|
||||
self._attribute_resolver = TaskAttributeResolver(llm)
|
||||
self._action_resolver = TaskActionResolver(llm)
|
||||
|
||||
def build(self, context: DocUpdateFromFeatureV2Context) -> list[RequirementTaskContext]:
|
||||
known_paths = {str(row.get("path") or "") for row in context.docs_catalog_rows}
|
||||
tasks: list[RequirementTaskContext] = []
|
||||
for index, requirement in enumerate(context.requirements):
|
||||
task = RequirementTaskContext(
|
||||
index=index,
|
||||
section_key=requirement.section_key or f"6.{index + 1}",
|
||||
heading=requirement.heading,
|
||||
body=requirement.body,
|
||||
metadata=dict(requirement.metadata),
|
||||
)
|
||||
self._attribute_resolver.resolve(context, task, rules_text="")
|
||||
task.path = self._path_resolver.resolve(
|
||||
application=task.application,
|
||||
platform=task.platform,
|
||||
doc_type=task.doc_type,
|
||||
doc_id=task.doc_id,
|
||||
domain=task.domain,
|
||||
)
|
||||
task.action = self._action_resolver.resolve(task, known_paths)
|
||||
known_paths.add(task.path)
|
||||
tasks.append(task)
|
||||
return tasks
|
||||
|
||||
|
||||
class RequirementTaskOrderer:
|
||||
_PLATFORM_PRIORITY = {"pprb": 0, "ufs": 1, "web": 2}
|
||||
|
||||
def order(self, tasks: list[RequirementTaskContext]) -> list[RequirementTaskContext]:
|
||||
return sorted(tasks, key=self._sort_key)
|
||||
|
||||
def _sort_key(self, task: RequirementTaskContext) -> tuple[int, int, int]:
|
||||
explicit_order = self._explicit_order(task.metadata)
|
||||
platform_order = self._PLATFORM_PRIORITY.get(task.platform, len(self._PLATFORM_PRIORITY))
|
||||
return explicit_order, platform_order, task.index
|
||||
|
||||
def _explicit_order(self, metadata: dict[str, object]) -> int:
|
||||
value = str(metadata.get("build_order") or metadata.get("order") or "").strip()
|
||||
return int(value) if value.isdigit() else 1000
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step4_prepare_tasks.services import (
|
||||
DocsCatalogLoader,
|
||||
RequirementTaskBuilder,
|
||||
RequirementTaskOrderer,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.utils.llm import AgentLlmService
|
||||
from app.core.agent.utils.workflow import WorkflowStep
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.core.rag.persistence.query_repository import RagQueryRepository
|
||||
|
||||
|
||||
class PrepareRequirementTasksStep(WorkflowStep[DocUpdateFromFeatureV2Context]):
|
||||
step_id = "step4_prepare_tasks"
|
||||
title = "Подготовка задач по страницам документации"
|
||||
|
||||
def __init__(self, llm: AgentLlmService, query_repository: "RagQueryRepository | None" = None) -> None:
|
||||
self._catalog_loader = DocsCatalogLoader(query_repository=query_repository)
|
||||
self._task_builder = RequirementTaskBuilder(llm)
|
||||
self._task_orderer = RequirementTaskOrderer()
|
||||
|
||||
async def run(self, context: DocUpdateFromFeatureV2Context) -> DocUpdateFromFeatureV2Context:
|
||||
if context.answer or not context.requirements:
|
||||
return context
|
||||
self._catalog_loader.load(context)
|
||||
context.requirement_tasks = self._task_orderer.order(self._task_builder.build(context))
|
||||
if not context.requirement_tasks:
|
||||
context.issues.append("Не удалось подготовить задачи по разделу 6 аналитики.")
|
||||
return context
|
||||
|
||||
def trace_input(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {
|
||||
"requirements_count": len(context.requirements),
|
||||
"requirements": [
|
||||
{"section_key": item.section_key, "heading": item.heading, "metadata": dict(item.metadata)}
|
||||
for item in context.requirements
|
||||
],
|
||||
}
|
||||
|
||||
def trace_output(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {
|
||||
"docs_catalog_rows_count": len(context.docs_catalog_rows),
|
||||
"tasks": [
|
||||
{
|
||||
"section_key": item.section_key,
|
||||
"heading": item.heading,
|
||||
"doc_id": item.doc_id,
|
||||
"doc_type": item.doc_type,
|
||||
"application": item.application,
|
||||
"platform": item.platform,
|
||||
"domain": item.domain,
|
||||
"sub_domain": item.subdomain,
|
||||
"action": item.action.value,
|
||||
"path": item.path,
|
||||
}
|
||||
for item in context.requirement_tasks
|
||||
],
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
"""Auto-generated package init."""
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class DeleteIntentHeuristic:
|
||||
_PATTERNS = (
|
||||
re.compile(r"\bdelete\b"),
|
||||
re.compile(r"\bremove\b"),
|
||||
re.compile(r"\bdecommission(?:ed|ing)?\b"),
|
||||
re.compile(r"\bудал(?:ить|ение|яем|яется|ен[аоы]?|ить страницу|ить документ)\b"),
|
||||
re.compile(r"\bдекомисс"),
|
||||
)
|
||||
|
||||
def is_delete(self, text: str) -> bool:
|
||||
lowered = (text or "").lower()
|
||||
return any(pattern.search(lowered) for pattern in self._PATTERNS)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class DocsPathResolver:
|
||||
def resolve(self, *, application: str, platform: str, doc_type: str, doc_id: str, domain: str = "") -> str:
|
||||
root = self._clean(domain) or self._clean(application) or "common"
|
||||
plat = self._clean(platform) or "web"
|
||||
dtype = self._clean(doc_type) or "misc"
|
||||
did = self._clean(doc_id) or "untitled"
|
||||
return f"docs/{root}/{plat}/{dtype}/{did}.md"
|
||||
|
||||
def _clean(self, value: str) -> str:
|
||||
normalized = re.sub(r"[^a-zA-Z0-9._-]+", "-", (value or "").strip().lower())
|
||||
return normalized.strip(".-")
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
namespace: v2_docs_update_v2
|
||||
|
||||
prompts:
|
||||
resolve_attributes_fallback: |
|
||||
Определи недостающие атрибуты страницы документации по секции аналитики и структуре docs catalog.
|
||||
Верни только JSON-объект с полями: doc_type, id, application, platform, domain, sub_domain.
|
||||
Не добавляй пояснений.
|
||||
|
||||
classify_action: |
|
||||
Классифицируй действие для страницы документации.
|
||||
Верни только JSON: {"action":"create|update|delete"}.
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step5_execute_subprocesses.classifier import (
|
||||
DeleteIntentHeuristic,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.create_doc import (
|
||||
CreateDocSubprocess,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.delete_doc import (
|
||||
DeleteDocSubprocess,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.edit_doc import EditDocSubprocess
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import (
|
||||
AccumulatedPageDraft,
|
||||
DocAction,
|
||||
RequirementTaskContext,
|
||||
)
|
||||
from app.core.agent.utils.llm import AgentLlmService
|
||||
from app.schemas.changeset import ChangeItem, ChangeOp
|
||||
|
||||
|
||||
class TaskAttributeResolver:
|
||||
def __init__(self, llm: AgentLlmService) -> None:
|
||||
self._llm = llm
|
||||
|
||||
def resolve(self, context: DocUpdateFromFeatureV2Context, task: RequirementTaskContext, rules_text: str) -> None:
|
||||
task.doc_type = str(task.metadata.get("doc_type") or task.metadata.get("type") or "").strip()
|
||||
task.doc_id = str(task.metadata.get("id") or self._slug(task.heading)).strip()
|
||||
task.application = str(task.metadata.get("application") or context.analytics_meta.application or "").strip()
|
||||
task.platform = str(task.metadata.get("platform") or context.analytics_meta.platform or "").strip().lower()
|
||||
task.domain = str(task.metadata.get("domain") or context.analytics_meta.domain or "").strip()
|
||||
task.subdomain = str(
|
||||
task.metadata.get("sub_domain") or task.metadata.get("subdomain") or context.analytics_meta.subdomain or ""
|
||||
).strip()
|
||||
missing = [
|
||||
name
|
||||
for name, value in [
|
||||
("doc_type", task.doc_type),
|
||||
("id", task.doc_id),
|
||||
("application", task.application),
|
||||
("platform", task.platform),
|
||||
("domain", task.domain),
|
||||
("sub_domain", task.subdomain),
|
||||
]
|
||||
if not value
|
||||
]
|
||||
if not missing:
|
||||
return
|
||||
payload = {
|
||||
"missing": missing,
|
||||
"full_document": context.source_content,
|
||||
"requirement": {"heading": task.heading, "body": task.body, "metadata": task.metadata},
|
||||
"docs_catalog_paths": [str(row.get("path") or "") for row in context.docs_catalog_rows[:300]],
|
||||
"rules": rules_text,
|
||||
}
|
||||
raw = self._llm.generate(
|
||||
"v2_docs_update_v2.resolve_attributes_fallback",
|
||||
json.dumps(payload, ensure_ascii=False, indent=2),
|
||||
log_context="workflow.v2.docs_update.from_feature_v2.resolve_attributes",
|
||||
)
|
||||
parsed = self._json_or_empty(raw)
|
||||
task.doc_type = task.doc_type or str(parsed.get("doc_type") or parsed.get("type") or "").strip()
|
||||
task.doc_id = task.doc_id or str(parsed.get("id") or self._slug(task.heading)).strip()
|
||||
task.application = task.application or str(parsed.get("application") or "").strip()
|
||||
task.platform = task.platform or str(parsed.get("platform") or "web").strip().lower()
|
||||
task.domain = task.domain or str(parsed.get("domain") or "").strip()
|
||||
task.subdomain = task.subdomain or str(parsed.get("sub_domain") or parsed.get("subdomain") or "").strip()
|
||||
|
||||
def _slug(self, value: str) -> str:
|
||||
return re.sub(r"[^a-z0-9._-]+", "-", (value or "").strip().lower()).strip(".-") or "untitled"
|
||||
|
||||
def _json_or_empty(self, raw: str) -> dict[str, object]:
|
||||
value = str(raw or "").strip()
|
||||
if not value:
|
||||
return {}
|
||||
try:
|
||||
payload = json.loads(value)
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
class TaskActionResolver:
|
||||
def __init__(self, llm: AgentLlmService) -> None:
|
||||
self._llm = llm
|
||||
self._delete_heuristic = DeleteIntentHeuristic()
|
||||
|
||||
def resolve(self, task: RequirementTaskContext, known_paths: set[str]) -> DocAction:
|
||||
explicit = self._explicit_action(task)
|
||||
if explicit is not None:
|
||||
return explicit
|
||||
if self._delete_heuristic.is_delete(task.body):
|
||||
return DocAction.DELETE
|
||||
return DocAction.EDIT if task.path in known_paths else DocAction.CREATE
|
||||
|
||||
def _explicit_action(self, task: RequirementTaskContext) -> DocAction | None:
|
||||
value = str(task.metadata.get("action") or task.metadata.get("op") or task.metadata.get("operation") or "")
|
||||
normalized = value.strip().lower()
|
||||
if normalized in {"create", "add", "new"}:
|
||||
return DocAction.CREATE
|
||||
if normalized in {"update", "edit", "modify"}:
|
||||
return DocAction.EDIT
|
||||
if normalized in {"delete", "remove"}:
|
||||
return DocAction.DELETE
|
||||
return None
|
||||
|
||||
class TaskChangeExecutor:
|
||||
def __init__(self, llm: AgentLlmService) -> None:
|
||||
self._create = CreateDocSubprocess(llm)
|
||||
self._edit = EditDocSubprocess(llm)
|
||||
self._delete = DeleteDocSubprocess()
|
||||
|
||||
def execute(self, context: DocUpdateFromFeatureV2Context, task: RequirementTaskContext) -> None:
|
||||
shared_context = self._build_shared_context(context)
|
||||
if task.action == DocAction.CREATE:
|
||||
result = self._create.run(task, rule_documents=context.rules, shared_context=shared_context)
|
||||
context.changeset.append(
|
||||
ChangeItem(op=ChangeOp.CREATE, path=task.path, proposed_content=result.content, reason=task.heading)
|
||||
)
|
||||
self._remember_change(context, task, result.content)
|
||||
return
|
||||
if task.action == DocAction.EDIT:
|
||||
current_content, base_hash = self._load_current_content(context, task.path)
|
||||
if not current_content or not base_hash:
|
||||
context.issues.append(f"Пропущен UPDATE для {task.path}: не найден текущий файл.")
|
||||
return
|
||||
result = self._edit.run(
|
||||
task,
|
||||
current_content=current_content,
|
||||
rule_documents=context.rules,
|
||||
shared_context=shared_context,
|
||||
)
|
||||
context.changeset.append(
|
||||
ChangeItem(
|
||||
op=ChangeOp.UPDATE,
|
||||
path=task.path,
|
||||
base_hash=base_hash,
|
||||
proposed_content=result.content,
|
||||
reason=task.heading,
|
||||
)
|
||||
)
|
||||
self._remember_change(context, task, result.content)
|
||||
return
|
||||
_, base_hash = self._load_current_content(context, task.path)
|
||||
if not base_hash:
|
||||
context.issues.append(f"Пропущен DELETE для {task.path}: не найден текущий файл.")
|
||||
return
|
||||
self._delete.run(task)
|
||||
context.changeset.append(ChangeItem(op=ChangeOp.DELETE, path=task.path, base_hash=base_hash, reason=task.heading))
|
||||
self._remember_change(context, task, "")
|
||||
|
||||
def _build_shared_context(self, context: DocUpdateFromFeatureV2Context) -> dict[str, object]:
|
||||
return {
|
||||
"analytics_meta": {
|
||||
"analysis_id": context.analytics_meta.analysis_id,
|
||||
"application": context.analytics_meta.application,
|
||||
"platform": context.analytics_meta.platform,
|
||||
"domain": context.analytics_meta.domain,
|
||||
"sub_domain": context.analytics_meta.subdomain,
|
||||
},
|
||||
"planned_pages": [
|
||||
{
|
||||
"section_key": item.section_key,
|
||||
"heading": item.heading,
|
||||
"path": item.path,
|
||||
"doc_id": item.doc_id,
|
||||
"doc_type": item.doc_type,
|
||||
"platform": item.platform,
|
||||
"application": item.application,
|
||||
"domain": item.domain,
|
||||
"sub_domain": item.subdomain,
|
||||
"action": item.action.value,
|
||||
}
|
||||
for item in context.requirement_tasks
|
||||
],
|
||||
"accumulated_pages": [
|
||||
{
|
||||
"path": item.path,
|
||||
"heading": item.heading,
|
||||
"doc_id": item.doc_id,
|
||||
"doc_type": item.doc_type,
|
||||
"platform": item.platform,
|
||||
"action": item.action.value,
|
||||
"content": item.content,
|
||||
}
|
||||
for item in context.accumulated_pages
|
||||
],
|
||||
}
|
||||
|
||||
def _load_current_content(self, context: DocUpdateFromFeatureV2Context, rel_path: str) -> tuple[str, str]:
|
||||
for item in reversed(context.accumulated_pages):
|
||||
if item.path == rel_path and item.action != DocAction.DELETE:
|
||||
content = item.content
|
||||
return content, hashlib.sha256(content.encode("utf-8")).hexdigest()
|
||||
root = Path(context.project_root or "")
|
||||
if not root.is_absolute():
|
||||
return "", ""
|
||||
target = root / rel_path
|
||||
if not target.exists() or not target.is_file():
|
||||
return "", ""
|
||||
content = target.read_text(encoding="utf-8")
|
||||
base_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()
|
||||
return content, base_hash
|
||||
|
||||
def _remember_change(self, context: DocUpdateFromFeatureV2Context, task: RequirementTaskContext, content: str) -> None:
|
||||
context.accumulated_pages = [item for item in context.accumulated_pages if item.path != task.path]
|
||||
context.accumulated_pages.append(
|
||||
AccumulatedPageDraft(
|
||||
path=task.path,
|
||||
heading=task.heading,
|
||||
doc_id=task.doc_id,
|
||||
doc_type=task.doc_type,
|
||||
platform=task.platform,
|
||||
action=task.action,
|
||||
content=content,
|
||||
)
|
||||
)
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step5_execute_subprocesses.services import (
|
||||
TaskChangeExecutor,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.utils.llm import AgentLlmService
|
||||
from app.core.agent.utils.workflow import WorkflowStep
|
||||
|
||||
|
||||
class ExecuteRequirementSubprocessesStep(WorkflowStep[DocUpdateFromFeatureV2Context]):
|
||||
step_id = "step6_execute_subprocesses"
|
||||
title = "Выполнение подпроцессов create/edit/delete"
|
||||
|
||||
def __init__(self, llm: AgentLlmService) -> None:
|
||||
self._change_executor = TaskChangeExecutor(llm)
|
||||
|
||||
async def run(self, context: DocUpdateFromFeatureV2Context) -> DocUpdateFromFeatureV2Context:
|
||||
if context.answer or not context.requirement_tasks:
|
||||
return context
|
||||
for task in context.requirement_tasks:
|
||||
self._change_executor.execute(context, task)
|
||||
return context
|
||||
|
||||
def trace_input(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {
|
||||
"tasks": [
|
||||
{
|
||||
"section_key": item.section_key,
|
||||
"heading": item.heading,
|
||||
"action": item.action.value,
|
||||
"path": item.path,
|
||||
"doc_type": item.doc_type,
|
||||
}
|
||||
for item in context.requirement_tasks
|
||||
],
|
||||
"rules_count": len(context.rules),
|
||||
}
|
||||
|
||||
def trace_output(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {
|
||||
"changeset": [
|
||||
{"op": item.op.value, "path": item.path, "reason": item.reason}
|
||||
for item in context.changeset
|
||||
],
|
||||
"accumulated_pages": [
|
||||
{
|
||||
"path": item.path,
|
||||
"doc_id": item.doc_id,
|
||||
"doc_type": item.doc_type,
|
||||
"action": item.action.value,
|
||||
}
|
||||
for item in context.accumulated_pages
|
||||
],
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
"""Auto-generated package init."""
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
|
||||
DocUpdateFromFeatureV2Context,
|
||||
)
|
||||
from app.core.agent.utils.workflow import WorkflowStep
|
||||
|
||||
|
||||
class FinalizeAnswerStep(WorkflowStep[DocUpdateFromFeatureV2Context]):
|
||||
step_id = "step7_finalize"
|
||||
title = "Формирование итогового ответа"
|
||||
|
||||
async def run(self, context: DocUpdateFromFeatureV2Context) -> DocUpdateFromFeatureV2Context:
|
||||
if context.answer:
|
||||
return context
|
||||
lines: list[str] = ["DOC_UPDATE/FROM_FEATURE v2: сформирован changeset."]
|
||||
if context.issues:
|
||||
lines.append("\nIssues:")
|
||||
for issue in context.issues:
|
||||
lines.append(f"- {issue}")
|
||||
lines.append("\nTasks:")
|
||||
for task in context.requirement_tasks:
|
||||
lines.append(
|
||||
f"- {task.section_key or f'#{task.index + 1}'} {task.action.value}: {task.path}"
|
||||
f" [{task.platform or '-'} | {task.doc_type or '-'}]"
|
||||
)
|
||||
if not context.requirement_tasks:
|
||||
lines.append("- Нет задач для обработки.")
|
||||
lines.append("\nChangeset:")
|
||||
lines.append("```json")
|
||||
lines.append(json.dumps([item.model_dump() for item in context.changeset], ensure_ascii=False, indent=2))
|
||||
lines.append("```")
|
||||
context.apply_changeset = bool(context.changeset)
|
||||
lines.append(f"\napply_changeset: {str(context.apply_changeset).lower()}")
|
||||
context.answer = "\n".join(lines)
|
||||
context.answer_generated_payload = {
|
||||
"answer_mode": "docs_update_changeset_v2",
|
||||
"changeset_items": len(context.changeset),
|
||||
"apply_changeset": context.apply_changeset,
|
||||
}
|
||||
return context
|
||||
|
||||
def trace_input(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {
|
||||
"tasks_count": len(context.requirement_tasks),
|
||||
"changeset_count": len(context.changeset),
|
||||
"issues_count": len(context.issues),
|
||||
}
|
||||
|
||||
def trace_output(self, context: DocUpdateFromFeatureV2Context) -> dict[str, Any]:
|
||||
return {
|
||||
"answer_generated_payload": dict(context.answer_generated_payload or {}),
|
||||
"answer_len": len(context.answer or ""),
|
||||
"apply_changeset": context.apply_changeset,
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
"""Auto-generated package init."""
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.models import (
|
||||
SubprocessResult,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.template_models import (
|
||||
SectionDraft,
|
||||
TemplateSection,
|
||||
TemplateSpec,
|
||||
)
|
||||
|
||||
__all__ = ["SectionDraft", "SubprocessResult", "TemplateSection", "TemplateSpec"]
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
import yaml
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.template_models import (
|
||||
SectionDraft,
|
||||
TemplateSpec,
|
||||
)
|
||||
|
||||
|
||||
class TemplateDocumentComposer:
|
||||
def compose(self, template: TemplateSpec, frontmatter: str, sections: list[SectionDraft], fallback_title: str) -> str:
|
||||
title = self._extract_title(frontmatter) or fallback_title
|
||||
section_map = {(item.level, item.title): item.content.strip() for item in sections}
|
||||
lines = [frontmatter.strip(), "", f"{'#' * template.title_level} {title}", ""]
|
||||
for section in template.sections:
|
||||
lines.append(f"{'#' * section.level} {section.title}")
|
||||
content = section_map.get((section.level, section.title), "")
|
||||
if content:
|
||||
lines.extend(["", content, ""])
|
||||
continue
|
||||
lines.append("")
|
||||
return "\n".join(lines).strip() + "\n"
|
||||
|
||||
def _extract_title(self, frontmatter: str) -> str:
|
||||
value = frontmatter.strip()
|
||||
match = re.match(r"^---\n(.*?)\n---$", value, re.DOTALL)
|
||||
if not match:
|
||||
return ""
|
||||
payload = yaml.safe_load(match.group(1)) or {}
|
||||
return str(payload.get("title") or "").strip() if isinstance(payload, dict) else ""
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import RequirementTaskContext
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class SubprocessResult:
|
||||
task: RequirementTaskContext
|
||||
content: str | None
|
||||
issue: str = ""
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import RuleDocument
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class RulesCatalog:
|
||||
by_name: dict[str, str]
|
||||
|
||||
@classmethod
|
||||
def from_documents(cls, rules: list[RuleDocument]) -> "RulesCatalog":
|
||||
return cls(by_name={item.name: item.content for item in rules})
|
||||
|
||||
def template_text(self, doc_type: str) -> str:
|
||||
return self.by_name.get(f"templates/{doc_type}.template.md", "")
|
||||
|
||||
def section_text(self, rule_path: str) -> str:
|
||||
return self.by_name.get(rule_path, "")
|
||||
|
||||
def global_text(self) -> str:
|
||||
parts: list[str] = []
|
||||
documentation_rules = self.by_name.get("documentation-rules.md", "").strip()
|
||||
if documentation_rules:
|
||||
parts.append("## documentation-rules.md")
|
||||
parts.append(documentation_rules)
|
||||
for name in sorted(self.by_name):
|
||||
if not name.startswith("global/"):
|
||||
continue
|
||||
value = self.by_name.get(name, "").strip()
|
||||
if not value:
|
||||
continue
|
||||
parts.append(f"## {name}")
|
||||
parts.append(value)
|
||||
return "\n\n".join(parts).strip()
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class RequirementSourceSections:
|
||||
_HEADING_RE = re.compile(r"^(#{2,6})\s+(.+?)\s*$")
|
||||
|
||||
def extract(self, text: str) -> dict[str, str]:
|
||||
sections: dict[str, str] = {}
|
||||
current_key = ""
|
||||
current_level = 0
|
||||
current_lines: list[str] = []
|
||||
for raw in text.splitlines():
|
||||
heading = self._match_heading(raw)
|
||||
if heading:
|
||||
level, title = heading
|
||||
if current_key and level <= current_level:
|
||||
sections[current_key] = "\n".join(current_lines).strip()
|
||||
current_key = ""
|
||||
current_lines = []
|
||||
normalized = self._normalize(title)
|
||||
if level >= 3:
|
||||
if current_key:
|
||||
sections[current_key] = "\n".join(current_lines).strip()
|
||||
current_key = normalized
|
||||
current_level = level
|
||||
current_lines = []
|
||||
continue
|
||||
if current_key:
|
||||
current_lines.append(raw)
|
||||
if current_key:
|
||||
sections[current_key] = "\n".join(current_lines).strip()
|
||||
return sections
|
||||
|
||||
def find_for_title(self, sections: dict[str, str], title: str) -> str:
|
||||
normalized = self._normalize(title)
|
||||
if normalized in sections:
|
||||
return sections[normalized]
|
||||
for key, value in sections.items():
|
||||
if key.startswith(normalized) or normalized.startswith(key):
|
||||
return value
|
||||
return ""
|
||||
|
||||
def _match_heading(self, raw: str) -> tuple[int, str] | None:
|
||||
match = self._HEADING_RE.match(raw.strip())
|
||||
if not match:
|
||||
return None
|
||||
return len(match.group(1)), match.group(2).strip()
|
||||
|
||||
def _normalize(self, value: str) -> str:
|
||||
text = value.lower().replace("ё", "е")
|
||||
text = text.replace("контракт метода", "контракт")
|
||||
text = text.replace("технический use case (тезисно)", "технический use case")
|
||||
text = re.sub(r"[^a-zа-я0-9]+", " ", text)
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import field
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class TemplateSection:
|
||||
title: str
|
||||
level: int
|
||||
rule_path: str = ""
|
||||
has_children: bool = False
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class TemplateSpec:
|
||||
doc_type: str
|
||||
source_name: str
|
||||
template_text: str
|
||||
title_level: int = 1
|
||||
sections: list[TemplateSection] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class SectionDraft:
|
||||
title: str
|
||||
level: int
|
||||
content: str
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
import yaml
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.template_models import (
|
||||
TemplateSection,
|
||||
TemplateSpec,
|
||||
)
|
||||
|
||||
|
||||
class TemplateParser:
|
||||
_HEADING_RE = re.compile(r"^(#{1,6})\s+(.+?)\s*$")
|
||||
_RULE_REF_RE = re.compile(r"`(\.\./common-elements/[^`]+|common-elements/[^`]+)`")
|
||||
|
||||
def parse(self, source_name: str, template_text: str) -> TemplateSpec:
|
||||
_, body = self._split_frontmatter(template_text)
|
||||
doc_type = self._doc_type(template_text, source_name)
|
||||
title_level = 1
|
||||
sections: list[TemplateSection] = []
|
||||
lines = body.splitlines()
|
||||
index = 0
|
||||
while index < len(lines):
|
||||
heading = self._match_heading(lines[index])
|
||||
if not heading:
|
||||
index += 1
|
||||
continue
|
||||
level, title = heading
|
||||
if "<title>" in title:
|
||||
title_level = level
|
||||
index += 1
|
||||
continue
|
||||
rule_path = self._extract_rule_path(lines, index + 1)
|
||||
sections.append(TemplateSection(title=title, level=level, rule_path=rule_path))
|
||||
index += 1
|
||||
self._mark_children(sections)
|
||||
return TemplateSpec(
|
||||
doc_type=doc_type,
|
||||
source_name=source_name,
|
||||
template_text=template_text,
|
||||
title_level=title_level,
|
||||
sections=sections,
|
||||
)
|
||||
|
||||
def _split_frontmatter(self, text: str) -> tuple[dict[str, object], str]:
|
||||
value = text.strip()
|
||||
if not value.startswith("---\n"):
|
||||
return {}, text
|
||||
_, _, tail = value.partition("---\n")
|
||||
frontmatter_text, _, body = tail.partition("\n---\n")
|
||||
payload = yaml.safe_load(frontmatter_text) or {}
|
||||
return payload if isinstance(payload, dict) else {}, body
|
||||
|
||||
def _doc_type(self, text: str, source_name: str) -> str:
|
||||
frontmatter, _ = self._split_frontmatter(text)
|
||||
doc_type = str(frontmatter.get("doc_type") or "").strip()
|
||||
if doc_type:
|
||||
return doc_type
|
||||
suffix = ".template.md"
|
||||
filename = source_name.split("/")[-1]
|
||||
return filename[: -len(suffix)] if filename.endswith(suffix) else filename
|
||||
|
||||
def _match_heading(self, line: str) -> tuple[int, str] | None:
|
||||
match = self._HEADING_RE.match(line.strip())
|
||||
if not match:
|
||||
return None
|
||||
return len(match.group(1)), match.group(2).strip()
|
||||
|
||||
def _extract_rule_path(self, lines: list[str], start: int) -> str:
|
||||
limit = min(len(lines), start + 4)
|
||||
for index in range(start, limit):
|
||||
match = self._RULE_REF_RE.search(lines[index])
|
||||
if not match:
|
||||
continue
|
||||
return match.group(1).replace("../", "")
|
||||
return ""
|
||||
|
||||
def _mark_children(self, sections: list[TemplateSection]) -> None:
|
||||
for index, section in enumerate(sections):
|
||||
if index + 1 >= len(sections):
|
||||
continue
|
||||
section.has_children = sections[index + 1].level > section.level
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.rules_catalog import (
|
||||
RulesCatalog,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.template_models import (
|
||||
TemplateSpec,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.template_parser import (
|
||||
TemplateParser,
|
||||
)
|
||||
|
||||
|
||||
class TemplateRegistry:
|
||||
def __init__(self, parser: TemplateParser | None = None) -> None:
|
||||
self._parser = parser or TemplateParser()
|
||||
|
||||
def load(self, catalog: RulesCatalog, doc_type: str) -> TemplateSpec:
|
||||
template_name = f"templates/{doc_type}.template.md"
|
||||
template_text = catalog.template_text(doc_type)
|
||||
if not template_text.strip():
|
||||
raise ValueError(f"Template not found for doc_type '{doc_type}'")
|
||||
return self._parser.parse(template_name, template_text)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.create_doc.process import (
|
||||
CreateDocSubprocess,
|
||||
)
|
||||
|
||||
__all__ = ["CreateDocSubprocess"]
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common import SubprocessResult
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.document_composer import (
|
||||
TemplateDocumentComposer,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.rules_catalog import (
|
||||
RulesCatalog,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.template_registry import (
|
||||
TemplateRegistry,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.create_doc.steps.step1_generate_frontmatter.step import (
|
||||
GenerateFrontmatterStep,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.create_doc.steps.step2_generate_sections.step import (
|
||||
GenerateSectionsStep,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.create_doc.steps.step4_compose_document.step import (
|
||||
ComposeDocumentStep,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import RequirementTaskContext
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import RuleDocument
|
||||
from app.core.agent.utils.llm import AgentLlmService
|
||||
|
||||
|
||||
class CreateDocSubprocess:
|
||||
def __init__(self, llm: AgentLlmService) -> None:
|
||||
self._frontmatter = GenerateFrontmatterStep(llm)
|
||||
self._sections = GenerateSectionsStep(llm)
|
||||
self._templates = TemplateRegistry()
|
||||
self._compose = ComposeDocumentStep(TemplateDocumentComposer())
|
||||
|
||||
def run(
|
||||
self,
|
||||
task: RequirementTaskContext,
|
||||
*,
|
||||
rule_documents: list[RuleDocument],
|
||||
shared_context: dict[str, object],
|
||||
) -> SubprocessResult:
|
||||
catalog = RulesCatalog.from_documents(rule_documents)
|
||||
template = self._templates.load(catalog, task.doc_type)
|
||||
frontmatter = self._frontmatter.run(task, template=template, catalog=catalog, shared_context=shared_context)
|
||||
sections = self._sections.run(task, template=template, catalog=catalog, shared_context=shared_context)
|
||||
content = self._compose.run(frontmatter, template, sections, fallback_title=task.heading)
|
||||
return SubprocessResult(task=task, content=content)
|
||||
+1
@@ -0,0 +1 @@
|
||||
"""Auto-generated package init."""
|
||||
+1
@@ -0,0 +1 @@
|
||||
"""Auto-generated package init."""
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace: v2_docs_update_v2
|
||||
|
||||
prompts:
|
||||
create_frontmatter: |
|
||||
Сгенерируй только frontmatter в markdown между --- и ---.
|
||||
Не добавляй другие секции и не добавляй пояснений.
|
||||
Используй requirement_text, template, global_rules, frontmatter_rules и shared_context.
|
||||
Обязательно заполни id, title, doc_type, status, domain, sub_domain, related_docs.
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.rules_catalog import (
|
||||
RulesCatalog,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.common.template_models import (
|
||||
TemplateSpec,
|
||||
)
|
||||
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import RequirementTaskContext
|
||||
from app.core.agent.utils.llm import AgentLlmService
|
||||
|
||||
|
||||
class GenerateFrontmatterStep:
|
||||
def __init__(self, llm: AgentLlmService) -> None:
|
||||
self._llm = llm
|
||||
|
||||
def run(
|
||||
self,
|
||||
task: RequirementTaskContext,
|
||||
*,
|
||||
template: TemplateSpec,
|
||||
catalog: RulesCatalog,
|
||||
shared_context: dict[str, object],
|
||||
) -> str:
|
||||
payload = {
|
||||
"task": {
|
||||
"section_key": task.section_key,
|
||||
"doc_type": task.doc_type,
|
||||
"id": task.doc_id,
|
||||
"application": task.application,
|
||||
"platform": task.platform,
|
||||
"domain": task.domain,
|
||||
"sub_domain": task.subdomain,
|
||||
"path": task.path,
|
||||
"heading": task.heading,
|
||||
},
|
||||
"requirement_text": task.body,
|
||||
"template": {"source_name": template.source_name, "template_text": template.template_text},
|
||||
"global_rules": catalog.global_text(),
|
||||
"frontmatter_rules": catalog.section_text("global/frontmatter.md"),
|
||||
"shared_context": shared_context,
|
||||
}
|
||||
return self._llm.generate(
|
||||
"v2_docs_update_v2.create_frontmatter",
|
||||
json.dumps(payload, ensure_ascii=False, indent=2),
|
||||
log_context="workflow.v2.docs_update.from_feature_v2.create.frontmatter",
|
||||
).strip()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user