Настройка процесса генерации документации
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"]
|
__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__ = [
|
__all__ = [
|
||||||
"AgentProcess",
|
"AgentProcess",
|
||||||
"ProcessResult",
|
"ProcessResult",
|
||||||
"V1Process",
|
"V1Process",
|
||||||
"V2Process",
|
"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"]
|
__all__ = ["V2IntentRouter", "V2Process"]
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name: str):
|
def __getattr__(name: str):
|
||||||
|
if name == "V2IntentRouter":
|
||||||
|
from app.core.agent.processes.v2.intent_router.router import V2IntentRouter
|
||||||
|
|
||||||
|
return V2IntentRouter
|
||||||
if name == "V2Process":
|
if name == "V2Process":
|
||||||
from app.core.agent.processes.v2.v2_process import 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 (
|
from app.core.agent.processes.v2.workflows.doc_update_from_feature.workflow_runtime.context import (
|
||||||
DocUpdateFromFeatureContext,
|
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.workflow_runtime.context import GeneralQaSummaryContext
|
||||||
from app.core.agent.processes.v2.workflows.general_qa_summary.graph import GeneralQaSummaryGraph
|
from app.core.agent.processes.v2.workflows.general_qa_summary.graph import GeneralQaSummaryGraph
|
||||||
from app.core.agent.utils.llm import AgentLlmService
|
from app.core.agent.utils.llm import AgentLlmService
|
||||||
@@ -44,6 +48,7 @@ class V2Process(AgentProcess):
|
|||||||
general_summary_prompt_name: str = "v2_general.summary_answer",
|
general_summary_prompt_name: str = "v2_general.summary_answer",
|
||||||
workflow_llm_enabled: bool = True,
|
workflow_llm_enabled: bool = True,
|
||||||
doc_rules_enabled: bool = True,
|
doc_rules_enabled: bool = True,
|
||||||
|
doc_update_workflow_version: str = "v2",
|
||||||
) -> None:
|
) -> None:
|
||||||
self._router = router or V2IntentRouter()
|
self._router = router or V2IntentRouter()
|
||||||
gate = evidence_gate or DocsEvidenceGate()
|
gate = evidence_gate or DocsEvidenceGate()
|
||||||
@@ -51,6 +56,8 @@ class V2Process(AgentProcess):
|
|||||||
self._general_summary_prompt_name = general_summary_prompt_name
|
self._general_summary_prompt_name = general_summary_prompt_name
|
||||||
self._workflow_llm_enabled = workflow_llm_enabled
|
self._workflow_llm_enabled = workflow_llm_enabled
|
||||||
self._doc_rules_enabled = doc_rules_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] = {
|
self._workflows: dict[tuple[str, str, str], Any] = {
|
||||||
(V2Domain.DOCS, V2Intent.DOC_EXPLAIN, V2Subintent.SUMMARY): DocExplainSummaryGraph(
|
(V2Domain.DOCS, V2Intent.DOC_EXPLAIN, V2Subintent.SUMMARY): DocExplainSummaryGraph(
|
||||||
llm,
|
llm,
|
||||||
@@ -69,10 +76,7 @@ class V2Process(AgentProcess):
|
|||||||
policy_resolver=policy_resolver,
|
policy_resolver=policy_resolver,
|
||||||
rag_adapter=rag_adapter,
|
rag_adapter=rag_adapter,
|
||||||
),
|
),
|
||||||
(V2Domain.DOCS, V2Intent.DOC_UPDATE, V2Subintent.FROM_FEATURE): DocUpdateFromFeatureGraph(
|
(V2Domain.DOCS, V2Intent.DOC_UPDATE, V2Subintent.FROM_FEATURE): doc_update_graph,
|
||||||
llm=llm,
|
|
||||||
doc_rules_enabled=doc_rules_enabled,
|
|
||||||
),
|
|
||||||
(V2Domain.GENERAL, V2Intent.GENERAL_QA, V2Subintent.SUMMARY): GeneralQaSummaryGraph(
|
(V2Domain.GENERAL, V2Intent.GENERAL_QA, V2Subintent.SUMMARY): GeneralQaSummaryGraph(
|
||||||
llm,
|
llm,
|
||||||
policy_resolver=policy_resolver,
|
policy_resolver=policy_resolver,
|
||||||
@@ -175,6 +179,7 @@ class V2Process(AgentProcess):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if route.intent == V2Intent.DOC_UPDATE and route.subintent == V2Subintent.FROM_FEATURE:
|
if route.intent == V2Intent.DOC_UPDATE and route.subintent == V2Subintent.FROM_FEATURE:
|
||||||
|
if self._doc_update_workflow_version == "legacy":
|
||||||
return await workflow.run(
|
return await workflow.run(
|
||||||
DocUpdateFromFeatureContext(
|
DocUpdateFromFeatureContext(
|
||||||
runtime=runtime_context,
|
runtime=runtime_context,
|
||||||
@@ -183,6 +188,13 @@ class V2Process(AgentProcess):
|
|||||||
doc_rules_enabled=self._doc_rules_enabled,
|
doc_rules_enabled=self._doc_rules_enabled,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
return await workflow.run(
|
||||||
|
DocUpdateFromFeatureV2Context(
|
||||||
|
runtime=runtime_context,
|
||||||
|
route=route,
|
||||||
|
rag_session_id=rag_session_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
return await workflow.run(
|
return await workflow.run(
|
||||||
DocExplainSummaryContext(
|
DocExplainSummaryContext(
|
||||||
runtime=runtime_context,
|
runtime=runtime_context,
|
||||||
@@ -192,3 +204,8 @@ class V2Process(AgentProcess):
|
|||||||
workflow_llm_enabled=self._workflow_llm_enabled,
|
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