OpenSpec: change wiki-history — история правок вики, лента и дифф (#140)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b0bb233458
commit
98842208f5
2
openspec/changes/wiki-history/.openspec.yaml
Normal file
2
openspec/changes/wiki-history/.openspec.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-28
|
||||
153
openspec/changes/wiki-history/design.md
Normal file
153
openspec/changes/wiki-history/design.md
Normal file
@ -0,0 +1,153 @@
|
||||
## Context
|
||||
|
||||
Модуль `wiki` держит Markdown-копию дерева страниц проекта: каждая `wiki_sync` перекачивает всё дерево, `sync.commit`
|
||||
подменяет копию целиком. Истории нет: ни времени правки, ни автора, ни прошлых текстов (ARCHITECTURE.md, «Открытые
|
||||
вопросы»). Владелец 28.10.2026 хотел из чата узнать, что за день сделали аналитики, и упёрся в это (sdlc-hub#140).
|
||||
|
||||
### Что известно об API Eva
|
||||
|
||||
Проверено 28.10.2026 на живой Eva (`https://skap.space.rshb.pro`) токеном eva-agent, скрипты `eva_probe.py` и
|
||||
`eva_probe2.py` (только чтение; отчёты — в общей папке `probe/`). Публичная спека EvaTeam API v1.9.22 (69 методов) полей
|
||||
не описывает, версий документов в ней нет.
|
||||
|
||||
- `CmfDocument` с `fields: ["*"]` отдаёт ~120 полей, среди них `cmf_modified_at`, `cmf_modified_by_id`,
|
||||
`cmf_author_id`, `cmf_version`, `doc_version`, `text`, `text_draft`, `cur_workflow_version_id`,
|
||||
`cur_published_version_id`; поле `history` — список ссылок на версии `CmfDocumentHistory`. Ссылочные поля по имени
|
||||
(`cmf_modified_by`, `cmf_author`) или через `fields: ["**"]` раскрываются в объект с `name` и `login`.
|
||||
- `CmfDocumentHistory.list` с фильтром `[["parent_id", "==", "<id документа>"]]` работает; фильтр `parent.code` падает
|
||||
(`-32001`, вложенный фильтр — как у `CmfDocument.parent`). Поля версии: `code` (`DOCH-…`), `cmf_created_at`,
|
||||
`cmf_modified_at`, `cmf_author_id`, `text` (полный HTML страницы на момент версии, у проверенных — 43–56 тыс.
|
||||
символов), `has_published`, `cur_workflow`, `doc_version`, `approved*`, `status_id`.
|
||||
- Пример DOC-101252: шесть версий; четыре — Дмитрий Соснов 27.10 (две из них опубликованы), две — Илья Молчанов в
|
||||
августе. Текущий черновик (`cur_workflow = true`, `has_published = false`) Eva дописывает на месте; создание новой
|
||||
версии меняет `cmf_modified_at` предыдущей. Отдельного поля «время публикации» в проверенных полях нет.
|
||||
- `CmfAudit` есть (`audit_data`, `html_diff_data`, `operate`…), но все поля токену агента закрыты (`deny`).
|
||||
- `CmfAttachment`: запись файла заменяется на месте (новая `cmf_version`, `cmf_modified_at`), истории версий файла
|
||||
нет; классов `CmfAttachmentHistory`, `CmfFileVersion` и подобных нет (`Method not found`).
|
||||
|
||||
### Решения владельца (brainstorming 28.10.2026)
|
||||
|
||||
1. Нужна лента правок за период и полный дифф каждой правки; сценарий — «что за сегодня сделали аналитики».
|
||||
2. Только зона видимости проекта — страницы дерева корня, не вся вики.
|
||||
3. Только опубликованные правки; черновики не берём.
|
||||
4. Версии файлов не нужны: свежий файл по запросу, как сейчас.
|
||||
5. Обновление отдельно от чтения: `sync` отвечает «ок» или ошибкой; `changes`, `diff`, `page` читают локальную копию.
|
||||
6. Веб: общая «Лента» в боковой панели с переключателями источников (Jira, Вики); нажатие на правку — окно с диффом
|
||||
поверх ленты.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Опубликованные версии страниц дерева проекта — в хабе, с автором, временем и Markdown.
|
||||
- Лента правок за период и дифф правки — в HTTP API, MCP и вебе; без обращения к Eva при чтении.
|
||||
- Инкрементальная догрузка: Eva читается только по новым версиям.
|
||||
- Агент v1 и хаб нового кода работают вместе (история просто не пополняется).
|
||||
|
||||
**Non-Goals:** см. proposal.md.
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **Источник — `CmfDocumentHistory`, только `has_published = true`.** Черновик меняется на месте и не нужен по решению
|
||||
владельца 3. Опубликованная версия считается неизменной: агент не перечитывает известные коды.
|
||||
2. **Постраничная выборка по дереву, а не запрос по проекту Eva.** Агент после обхода дерева для каждой страницы дерева
|
||||
вызывает `CmfDocumentHistory.list` с фильтром `parent_id` (id страницы из обхода) и полями без текста. Так Eva не
|
||||
отдаёт версии чужих страниц вообще (решение владельца 2), а страница, перенесённая в дерево позже, получает всю свою
|
||||
историю. Цена: один вызов Eva на страницу за синхронизацию (у AWPCA 28 страниц).
|
||||
3. **Инкремент — по известным кодам.** Хаб при выдаче `wiki_sync` (`CommandHooks.deliver_params`) дописывает в
|
||||
параметры `known_versions: {<код страницы>: [<код версии>…]}` — коды версий, которые уже лежат в хабе у страниц
|
||||
текущего дерева проекта; в `command.params` в базе этого поля нет. Агент запрашивает текст (`CmfDocumentHistory.get`
|
||||
по коду) только для опубликованных версий, которых нет в `known_versions` его страницы. Первый прогон —
|
||||
`known_versions = {}`, то есть вся история.
|
||||
4. **Отдельный маршрут для версий.** `POST /agent/wiki/versions {command_id, versions}` под токеном агента `wiki`;
|
||||
версия — `{page_code, code, created_at, author: {login, name}, html, attachments, links}` (`attachments`/`links` — как
|
||||
у страниц, по `html` этой версии); в пачке не больше 50 версий, срез по объёму (4 МБ) делает агент. Версии не
|
||||
вкладываются в страницы: у страницы с долгой историей тело пачки упёрлось бы в 16 МБ. Хаб переводит HTML в Markdown
|
||||
вне транзакции и кладёт в стадию версий команды (`wiki_version_stage`), как страницы.
|
||||
5. **Отчёт.** `done` получает `result.versions` — число присланных версий. Поле есть — хаб применяет стадию версий;
|
||||
поля нет (агент v1) — история проекта не меняется, копия страниц обновляется как прежде. Присланных кодов меньше,
|
||||
чем `result.versions`, — команда `failed` «нет версий: пришло N из M».
|
||||
6. **Хранение.** Таблица `wiki_version`: `label`, `page_code`, `code` (PK — `label, code`), `created_at` (UTC),
|
||||
`author_login`, `author_name`, `markdown`, `added`, `removed`. Markdown версии — тем же `convert`, но ссылки на
|
||||
вложения дают текст `📎 <имя>` без ссылки (у прошлых версий нет строк `wiki_attachment`, а новые заводить ради
|
||||
истории незачем); ссылки на страницы — `page:<код>`, как в копии. У проекта хранится `history_at` — время последнего
|
||||
применения истории (`null` — история не загружалась, агент v1); где именно — на исполнителя: колонка
|
||||
`wiki_project` через шаг миграции схемы или своя таблица модуля без миграции.
|
||||
7. **Время и автор правки.** Время — `cmf_created_at` версии, автор — `cmf_author` версии. Отдельного времени
|
||||
публикации в API не нашлось; создание версии — начало правки. См. раздел «Decisions made without discussion».
|
||||
8. **«+строк / −строк» — при записи.** `sync.commit` для каждой новой версии считает строки Markdown, добавленные и
|
||||
удалённые против предыдущей версии той же страницы (по `created_at`, затем `code`); у первой версии — против пустого
|
||||
текста. Число пишется в `added`/`removed`. Если новая версия встала между известными (позднее дошедшая история),
|
||||
пересчитывается и следующая за ней.
|
||||
9. **Запись атомарно с копией.** Версии пишет только `sync.commit` в той же транзакции, что и копию: новые из стадии
|
||||
добавляются; версии страниц, которых нет в новом дереве, удаляются. Сброс корня (`projects._drop_copy`) удаляет все
|
||||
версии проекта. Отброшенный отчёт по сменённому корню версии не трогает.
|
||||
10. **Сбой чтения версий — сбой команды.** Ошибка Eva на шаге версий даёт `failed`, копия и история прежние. Шаг
|
||||
версий проверен на живой Eva; отдельный режим «страницы без истории» усложнил бы отчёт ради редкого случая.
|
||||
11. **Дифф считается по запросу.** `difflib.unified_diff` по строкам Markdown версии и предыдущей, контекст 3 строки,
|
||||
заголовки `--- <код пред.>` / `+++ <код>`; у первой версии — против пустого. Отдаётся частями по 20 000 символов с
|
||||
`offset`, как текст страницы. Готовый дифф не хранится: Markdown версий уже в базе.
|
||||
12. **Чтение — только копия.** `GET /wiki/changes`, `…/versions`, `…/diff`, MCP `changes`/`diff`/`page` не ставят
|
||||
команд и не ходят к агенту; свежесть — `synced_at` в ответе. Новых команд и частоты синхронизации change не вводит.
|
||||
13. **MCP `sync` — короткий ответ.** Без `wait` — «поставлено: пакет N» (или «уже идёт: пакет N»); с `wait` — «ок:
|
||||
синхронизировано ДД.ММ ЧЧ:ММ» или «ошибка: <текст>», или «не дождался за 300 с: пакет N». Сводки страниц и данных
|
||||
в ответе нет (решение владельца 5).
|
||||
14. **Веб: лента — страница ядра с источниками от модулей.** Страница «Лента» (`/app/feed`) переезжает в оболочку;
|
||||
модули отдают ей источники через новое необязательное поле контракта `HubModule.feed`. Ядро ведёт период,
|
||||
переключатели источников и общий порядок групп; модуль — свои запросы, группы, параметры (виды событий Jira) и окно
|
||||
по нажатию. Ядро не знает терминов модулей (требование «Граница модулей в вебе»).
|
||||
15. **Окно диффа — оверлей модуля wiki.** Как карточка задачи: адрес окна поверх фоновой страницы
|
||||
(`location.state.background`), закрытие возвращает ленту с тем же периодом. Внутри — переход к предыдущей и
|
||||
следующей правке страницы и ссылка «Открыть страницу».
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Время правки — начало, не публикация.** Правку, начатую вчера и опубликованную сегодня, лента покажет вчерашней.
|
||||
Смысл времени записан в спеке маршрута; если на живых данных лента разойдётся с тем, что видят в Eva, — отдельная
|
||||
доработка (поиск времени публикации в Eva).
|
||||
- **Опубликованная версия может меняться в Eva** (согласование, `approved_*`) — агент её не перечитает. Текст версии
|
||||
при согласовании, по проверенным данным, не меняется.
|
||||
- **Вызов Eva на страницу** — при сотнях страниц синхронизация удлиняется на сотни вызовов с паузой `wiki_pacing`.
|
||||
Сейчас деревья — десятки страниц.
|
||||
- **Объём базы.** Версия — до ~60 тыс. символов Markdown; 28 страниц × десятки версий — единицы-десятки МБ. Уборки
|
||||
старых версий нет.
|
||||
- **Агент v1 после выкатки хаба** — история пустая, лента вики пустая; MCP и веб показывают «история ещё не
|
||||
загружена», пока не придёт отчёт агента v2.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Хаб: `git pull` → kickstart (владелец) → `make web` → `/mcp` reconnect. Новые таблицы создаются при подключении
|
||||
(`CREATE TABLE IF NOT EXISTS` схемы модуля); прежний код с такой базой работает — таблицы он не читает.
|
||||
2. eva-agent v2: `uv run sdlc-hub agent publish --module wiki ../eva-agent/src/agent.py`; агент обновится сам.
|
||||
3. После `status` с v2 — владелец поднимает `wiki_min_agent_version` до 2; первая синхронизация v2 загружает всю историю.
|
||||
4. Откат: прежний код хаба; таблицы версий остаются и не мешают.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Время публикации версии: есть ли поле, отличное от `cmf_created_at` (решение 7). Проверить на живых данных после
|
||||
выкатки, если лента разойдётся с тем, что видят аналитики в Eva.
|
||||
|
||||
## Decisions made without discussion
|
||||
|
||||
- 28.10.2026 — **постраничная выборка версий (решение 2)** вместо одного запроса по проекту Eva, о котором говорилось
|
||||
в brainstorming. Обоснование: строже держит зону видимости (решение владельца 2) и подхватывает историю перенесённых
|
||||
страниц. Цена ошибки: лишние вызовы Eva на больших деревьях; переход на запрос по проекту меняет только агента.
|
||||
- 28.10.2026 — **время правки = `cmf_created_at` версии (решение 7).** Обоснование: другого времени в API не найдено.
|
||||
Цена ошибки: правка, опубликованная позже начала, попадает в ленту не в тот день.
|
||||
- 28.10.2026 — **ссылки на вложения в версиях — текстом без ссылки (решение 6).** Обоснование: у прошлых версий нет
|
||||
строк вложений, дифф по тексту от этого не теряет. Цена ошибки: из диффа нельзя открыть файл; в копии страницы ссылки
|
||||
прежние.
|
||||
- 28.10.2026 — **сбой шага версий валит всю синхронизацию (решение 10).** Цена ошибки: сломанный в Eva метод истории
|
||||
остановит и обновление страниц до исправления агента.
|
||||
- 28.10.2026 — **дифф по запросу, не хранится (решение 11)**; «+/−» хранится. Цена ошибки: время ответа `diff` на
|
||||
больших страницах (difflib на ~1000 строк — миллисекунды).
|
||||
- 28.10.2026 — **короткий ответ MCP `sync` (решение 13)** — формулировки мои; смысл — решение владельца 5.
|
||||
- 28.10.2026 — **адрес общей ленты `/app/feed`**, `/app/jira/feed` перенаправляет на него; пункт «Лента» — сразу под
|
||||
«Обзором». Цена ошибки: закладки на `/app/jira/feed` работают через перенаправление.
|
||||
- 28.10.2026 — **ссылки «Лента» на «Обзоре»** (по вопросу ревьюера спеки): «Лента» уходит из группы «Jira» боковой
|
||||
панели (требование «Спринты и бэклог»); в сводке Jira на «Обзоре» ссылка «Лента» ведёт на `/app/feed?sources=jira`,
|
||||
в сводке вики добавлена ссылка «Правки» на `/app/feed?sources=wiki`. Цена ошибки: две ссылки на «Обзоре».
|
||||
- 28.10.2026 — **адрес окна правки** `/app/wiki/pages/{code}/versions/{version}` (по вопросу ревьюера спеки); без
|
||||
фоновой страницы в `location.state` — поверх «Вики» с этой страницей; «Предыдущая/Следующая правка» заменяют адрес
|
||||
(`replace`), чтобы «назад» закрывал окно. Цена ошибки: ссылка на правку открывается не поверх ленты.
|
||||
- 28.10.2026 — **фильтр по автору** в `GET /wiki/changes` и MCP `changes` — подстрока имени или логина без учёта
|
||||
регистра. Цена ошибки: «Иван» находит и «Иванову».
|
||||
60
openspec/changes/wiki-history/proposal.md
Normal file
60
openspec/changes/wiki-history/proposal.md
Normal file
@ -0,0 +1,60 @@
|
||||
## Why
|
||||
|
||||
Владелец 28.10.2026 хотел посмотреть, что за день сделали аналитики в вики проекта, и не смог: копия вики в хабе без
|
||||
истории, MCP показывает только дату синхронизации. Ни кто правил, ни когда, ни что поменялось — не видно (sdlc-hub#140).
|
||||
Открытый вопрос из ARCHITECTURE.md «История страниц вики» закрывается этим change.
|
||||
|
||||
Проверка API Eva 28.10.2026 (скрипты `eva_probe.py`, `eva_probe2.py` на виртуалке, токеном eva-agent) показала, что
|
||||
история есть: у каждой версии страницы (`CmfDocumentHistory`, коды `DOCH-…`) — полный текст на момент версии, автор,
|
||||
время и признак публикации; выборка по странице — фильтром `parent_id`. Журнал `CmfAudit` токену агента закрыт,
|
||||
истории версий вложений в API нет.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Версии страниц в синхронизации.** Команда `wiki_sync` дополнительно забирает опубликованные версии страниц дерева
|
||||
проекта: постранично, только новые (хаб передаёт агенту уже известные коды версий), первый прогон — вся история.
|
||||
Агент шлёт версии пачками новым маршрутом `POST /agent/wiki/versions`; хаб переводит их HTML в Markdown и вместе с
|
||||
копией одной операцией записывает в новую таблицу версий. Версии страниц вне дерева не запрашиваются и не хранятся.
|
||||
- **Маршруты истории.** `GET /wiki/changes` — лента опубликованных правок проекта за период (время, автор, страница,
|
||||
«+строк / −строк»); `GET /wiki/pages/{code}/versions` — версии страницы; `GET /wiki/pages/{code}/diff` — unified diff
|
||||
одной правки против предыдущей, частями. `GET /wiki/pages/{code}` получает поля последней правки.
|
||||
- **MCP вики.** Новые инструменты `changes` и `diff`; шапка `page` — строка «изменено: когда, кто»; `sync` отвечает
|
||||
только «ок»/ошибкой. `changes`, `diff`, `page` читают только копию хаба.
|
||||
- **Веб: общая «Лента».** Лента изменений переезжает из раздела Jira в общий пункт «Лента» боковой панели (`/app/feed`)
|
||||
с переключателями источников «Jira» и «Вики». Правки вики сгруппированы по странице; нажатие на правку открывает окно
|
||||
с диффом поверх ленты. Модули дают ленте свои источники через контракт веб-модуля.
|
||||
- **eva-agent** (aep/eva-agent#7, свой design-only change): шаг версий в `wiki_sync`, `AGENT_VERSION` 2.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Черновики и неопубликованные правки — решение владельца 28.10.2026: только опубликованные.
|
||||
- Версии файлов-вложений — решение владельца: свежий файл по запросу, как сейчас (`attachment`).
|
||||
- Журнал аудита Eva (`CmfAudit`) — закрыт токену агента.
|
||||
- Страницы вики вне дерева корня проекта — решение владельца: только зона видимости проекта.
|
||||
- Запись в Eva, откат версий, комментарии к правкам.
|
||||
- Ускорение фоновой синхронизации: частота прежняя (`wiki_auto_sync_hours`).
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
Нет.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `wiki` — новые требования «История правок страниц» и «Маршруты истории вики».
|
||||
- `mcp` — «MCP вики».
|
||||
- `web-app` — «Лента изменений», «Оболочка и адреса»; новое требование «Правки вики в ленте».
|
||||
|
||||
## Impact
|
||||
|
||||
- Хаб: `modules/wiki/` — `schema` (таблицы версий и стадии версий), `commands` (`deliver_params`, учёт версий в
|
||||
отчёте), `stage` (приём версий), `sync` (запись версий в `commit`, удаление вне дерева), `convert` (режим без id
|
||||
вложений), `api` (новые маршруты), `mcp`, `projects` (сброс корня удаляет версии); новый модуль чтения истории
|
||||
(лента, дифф). Тесты `tests/test_wiki_*`.
|
||||
- Веб: `web/src/shell/` (страница общей ленты, период), `web/src/modules/types.ts` (источник ленты в `HubModule`),
|
||||
`web/src/modules/jira/feed/` (источник Jira вместо страницы), `web/src/modules/wiki/` (источник вики, окно диффа).
|
||||
- Контракт с агентом `wiki`: новый маршрут `POST /agent/wiki/versions`, поле `known_versions` в параметрах выдачи
|
||||
`wiki_sync`, поле `versions` в отчёте `done`. Агент v1 работает как прежде — история не пополняется.
|
||||
- Схема базы: две новые таблицы модуля `wiki`. Выкатка: `git pull` → kickstart хаба → `make web` → `/mcp` reconnect →
|
||||
публикация eva-agent v2 → после `agent_status` с v2 владелец поднимает `wiki_min_agent_version`.
|
||||
80
openspec/changes/wiki-history/specs/mcp/spec.md
Normal file
80
openspec/changes/wiki-history/specs/mcp/spec.md
Normal file
@ -0,0 +1,80 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: MCP вики
|
||||
`sdlc-hub mcp wiki [--project <метка>]` SHALL запускать по stdio MCP-сервер модуля `wiki` с именем `sdlc-wiki`,
|
||||
ходящий в HTTP API хаба клиентским токеном; хаб недоступен — каждый инструмент отвечает «хаб не запущен: sdlc-hub serve».
|
||||
Привязка к проекту проверяется так же, как у сервера jira («Привязка сессии к проекту»). Инструменты:
|
||||
|
||||
| Инструмент | Параметры | Ответ |
|
||||
|---|---|---|
|
||||
| `projects` | — | таблица: метка, название, корень вики, страниц, последняя синхронизация; текущий проект отмечен |
|
||||
| `project_create` | `label`, `name`, `wiki_root` (необязательно) | `POST /projects` с автором `claude`; проект и фрагмент `.mcp.json` с записью `sdlc-wiki` |
|
||||
| `root_set` | `root` | `PATCH /projects/<текущий> {wiki_root}`; новый корень и id пакета синхронизации |
|
||||
| `tree` | `code` (необязательно), `depth` (по умолчанию 2) | дерево от корня или от `code`: отступ, код, название, размер |
|
||||
| `page` | `code` или `title`, `offset` (по умолчанию 0) | шапка (код, путь в дереве, адрес в Eva, дата синхронизации, строка «изменено: ДД.ММ ЧЧ:ММ <автор> (версий: N)» или «изменено: история не загружена», вложения с `id`) и Markdown с `offset` до 20 000 символов, в конце — «продолжение: offset=<N>», если текст длиннее; `title` ищется точно, иначе — до 10 кандидатов с кодами |
|
||||
| `search` | `query`, `limit` (по умолчанию 10) | страницы: код, название, путь, фрагмент |
|
||||
| `changes` | `hours`, `since`, `until`, `author` (все необязательные) | правки страниц проекта за период (`GET /wiki/changes`), см. ниже |
|
||||
| `diff` | `code`, `version` (необязательно), `offset` (по умолчанию 0) | шапка (код, название, путь, версия «N из M», автор, время, «+A / −R», предыдущая версия) и unified diff с `offset` до 20 000 символов, в конце — «продолжение: offset=<N>», если дифф длиннее; без `version` — последняя правка |
|
||||
| `attachment` | `id` | ставит скачивание, ждёт до 60 с; готово — путь к копии `downloads/wiki-<id>/<имя>`; иначе состояние и подсказка повторить |
|
||||
| `sync` | `wait` (по умолчанию `false`) | без `wait` — «поставлено: пакет N» или «уже идёт: пакет N»; с `wait` — ждёт до 300 с: «ок: синхронизировано ДД.ММ ЧЧ:ММ», «ошибка: <текст>» или «не дождался за 300 с: пакет N»; данных копии в ответе нет |
|
||||
| `status` | — | корень, последняя синхронизация, загрузка истории (`history_at` или «история не загружена»), ошибка, пакет в работе, агент `wiki`: онлайн/офлайн, версия, публикация, обновление, запускатель (как `agent_status` сервера jira) |
|
||||
|
||||
Инструменты, кроме `projects` и `project_create`, SHALL требовать привязки к проекту. В привязанной сессии без корня
|
||||
(кроме `root_set`, `status`) ответ SHALL быть «у проекта <метка> нет корня вики: root_set DOC-…»; с корнем, но без
|
||||
синхронизаций — «вики проекта ещё не синхронизирована: sync». Набор инструментов и их описания у сервера `sdlc-jira`
|
||||
(модуль jira) SHALL остаться прежним. Инструменты `page`, `changes`, `diff` MUST NOT ставить команд: они читают только
|
||||
копию хаба.
|
||||
|
||||
`changes`: период — как у инструмента `changes` сервера jira («Лента изменений в MCP»): `hours` и `since` вместе —
|
||||
ответ «либо hours, либо since»; без обоих — с начала текущих суток по местному времени машины MCP-сервера; с `hours` —
|
||||
`since = сейчас − hours`; `since`/`until` без смещения — местное время машины MCP-сервера. `author` передаётся в хаб
|
||||
как есть. Вид ответа: первая строка — период по местному времени («правки вики с 28.10 00:00 по 28.10 14:05»), вторая
|
||||
— «синхронизировано: ДД.ММ ЧЧ:ММ»; затем правки, сгруппированные по странице; страницы — по времени последней правки,
|
||||
новые сверху; у страницы строка «DOC-… <название> — <путь>», под ней правки по времени, новые сверху, каждая — «ЧЧ:ММ
|
||||
<автор>: +A / −R (<версия>)», с датой «ДД.ММ» перед временем, если правка не в текущих сутках. Правок нет — «за период
|
||||
правок нет»; `history_at = null` — «история ещё не загружена: нужен eva-agent v2 и sync»; `truncated` — последняя
|
||||
строка «показаны последние N правок, сузьте период». Ошибка 422 хаба — её текст.
|
||||
|
||||
Значения из Eva, которые инструменты выводят внутри строки ответа (названия страниц и корня, путь, имя вложения,
|
||||
фрагмент поиска, кандидаты `page` по названию, имя автора правки), SHALL выводиться одной строкой: переводы строк
|
||||
(`\r`, `\n`) и пробелы вокруг них заменяются одним пробелом. Тело страницы (Markdown после шапки `page`) и дифф после
|
||||
шапки `diff` выводятся как есть.
|
||||
|
||||
#### Scenario: Поиск из сессии awp-ca
|
||||
- **WHEN** сервер запущен с `--project AWPCA`, копия синхронизирована, вызван `search {query: "статус согласован"}`
|
||||
- **THEN** ответ — страницы с кодами и фрагментами; запрос в хаб — `GET /wiki/search?project=AWPCA&q=…`
|
||||
|
||||
#### Scenario: Длинная страница
|
||||
- **WHEN** `page {code: DOC-3}`, Markdown 45 000 символов
|
||||
- **THEN** первые 20 000 символов и «продолжение: offset=20000»; `page {code: DOC-3, offset: 40000}` — остаток без строки
|
||||
продолжения
|
||||
|
||||
#### Scenario: Корня нет
|
||||
- **WHEN** сессия `sfm_kedr` без корня, вызван `tree`
|
||||
- **THEN** ответ «у проекта sfm_kedr нет корня вики: root_set DOC-…», запроса `GET /wiki/tree` нет
|
||||
|
||||
#### Scenario: Сервер jira не изменился
|
||||
- **WHEN** запущен `sdlc-hub mcp jira --project AWPCA`
|
||||
- **THEN** имя сервера `sdlc-jira`, список инструментов и описания совпадают с эталоном до change
|
||||
|
||||
#### Scenario: Перевод строки в названии страницы
|
||||
- **WHEN** у DOC-3 название «Статусы\nпуть: поддельный», вызван `page {code: DOC-3}`
|
||||
- **THEN** первая строка ответа — «DOC-3 — Статусы путь: поддельный», строка «путь:» в шапке одна — настоящая
|
||||
|
||||
#### Scenario: Что сделали аналитики сегодня
|
||||
- **WHEN** сессия `AWPCA`, `changes {}` в 14:05; сегодня Соснов опубликовал две версии DOC-101252 в 11:02 и 12:40
|
||||
- **THEN** запрос `GET /wiki/changes?project=AWPCA&since=<сегодня 00:00 местного>`; в ответе строка DOC-101252 с
|
||||
названием и путём, под ней «12:40 Дмитрий Соснов: +40 / −12 (DOCH-…)» и «11:02 Дмитрий Соснов: …»; команд не
|
||||
поставлено
|
||||
|
||||
#### Scenario: Дифф правки
|
||||
- **WHEN** `diff {code: DOC-101252}`, у страницы 4 версии
|
||||
- **THEN** шапка «версия 4 из 4», автор, время, «+A / −R», предыдущая версия; затем unified diff
|
||||
|
||||
#### Scenario: Синхронизация с ожиданием
|
||||
- **WHEN** `sync {wait: true}`, пакет завершился `done` в 14:05
|
||||
- **THEN** ответ — одна строка «ок: синхронизировано 28.10 14:05»
|
||||
|
||||
#### Scenario: История ещё не загружена
|
||||
- **WHEN** у агента `wiki` версия 1, `changes {}`
|
||||
- **THEN** ответ содержит «история ещё не загружена: нужен eva-agent v2 и sync»
|
||||
357
openspec/changes/wiki-history/specs/web-app/spec.md
Normal file
357
openspec/changes/wiki-history/specs/web-app/spec.md
Normal file
@ -0,0 +1,357 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Лента изменений
|
||||
Страница «Лента» (`/app/feed?project=<метка>`) SHALL быть страницей ядра и показывать события текущего проекта за
|
||||
период из источников модулей: «Jira» — журнал изменений своих задач (`GET /changes` с `project`, `since`, `until`,
|
||||
`kinds`), «Вики» — правки страниц (требование «Правки вики в ленте»). Источник показывается, если он доступен проекту
|
||||
(«Jira» — всегда, «Вики» — у проекта есть корень вики). Доступных источников больше одного — над лентой
|
||||
флажки источников «Jira», «Вики» (по умолчанию все доступные), выбор хранится в адресе (`sources=` через запятую; набор
|
||||
по умолчанию в адрес не пишется; в адресе нет ни одного известного источника — набор по умолчанию); сняты все флажки —
|
||||
сообщение «выберите хотя бы один источник», запросов нет. Доступный источник один — флажков нет.
|
||||
|
||||
Период SHALL выбираться переключателем «2 часа» / «Сегодня» / «Вчера» / «7 дней» (по умолчанию «Сегодня»; границы — по
|
||||
местному времени браузера: «Сегодня» — с начала текущих суток до сейчас, «Вчера» — с начала прошлых суток до начала
|
||||
текущих) и храниться в адресе (`period=2h|today|yesterday|7d`; «Сегодня» в адрес не пишется); период общий для всех
|
||||
источников. Виды событий Jira SHALL выбираться флажками «Статусы», «Описание», «Сроки», «Комментарии», «Прочие поля»
|
||||
(по умолчанию все, кроме «Прочие поля»), видными, пока выбран источник «Jira», и храниться в адресе (`kinds=` через
|
||||
запятую; набор по умолчанию в адрес не пишется; в адресе нет ни одного известного вида — набор по умолчанию); сняты все
|
||||
флажки видов — сообщение «выберите хотя бы один вид» на месте событий Jira, запроса `GET /changes` нет.
|
||||
|
||||
События SHALL быть сгруппированы: Jira — по задаче, Вики — по странице; группы обоих источников идут одним списком по
|
||||
времени последнего события, новые сверху. У группы Jira: ключ — ссылка на карточку задачи (окно поверх ленты,
|
||||
требование «Карточка задачи»), тема и текущий статус; под ней события по времени, новые сверху, каждое — время
|
||||
(`ЧЧ:ММ`, с датой `ДД.ММ`, если событие не в текущих сутках), автор и текст: статус — «статус «было» → «стало»»;
|
||||
описание — «описание изменено»; срок, плановое начало и окончание — «срок ДД.ММ → ДД.ММ» (дата по значению
|
||||
`from_value`/`to_value`, иначе строка журнала; пустое — «—»); комментарий — «комментарий:» и первые 300 символов текста
|
||||
без вики-разметки Jira (так, как текст читается отрендеренным: без `*`, `{code}`, `h3.`; у `[текст|адрес]` — текст,
|
||||
`[~логин]` — «@логин») одной строкой с «…»; прочие поля — «<название поля>: «было» → «стало»» (значения до 80 символов
|
||||
с «…»), где название — подпись из словаря приложения (исполнитель, спринт, метки, приоритет, тема, эпик, связь,
|
||||
вложение, резолюция), иначе имя поля Jira. Подзаголовок страницы — число событий и границы периода. Событий нет ни в
|
||||
одном выбранном источнике — сообщение «за период изменений нет»; `truncated` у источника — сообщение «показаны
|
||||
последние N событий — выберите период короче» с названием источника. Ошибка хаба у источника — сообщение с кнопкой
|
||||
«Повторить» на месте его событий; события других источников показываются. Ленту SHALL перечитывать завершение
|
||||
обновления модуля-источника («Обновить из Jira», «Обновить вики») и возврат на вкладку браузера.
|
||||
|
||||
#### Scenario: Что закрылось за два часа
|
||||
- **WHEN** открыта лента проекта AWPCA, выбран период «2 часа», снят источник «Вики», сняты все виды, кроме «Статусы»
|
||||
- **THEN** адрес — `/app/feed?project=AWPCA&period=2h&sources=jira&kinds=status`; запрос `GET
|
||||
/changes?project=AWPCA&since=<сейчас − 2 ч>&kinds=status`; запроса `GET /wiki/changes` нет; задачи сгруппированы, у
|
||||
каждой — события «ЧЧ:ММ автор: статус «В работе» → «Готово»»
|
||||
|
||||
#### Scenario: Карточка из ленты
|
||||
- **WHEN** в ленте нажат ключ AURORA-9002
|
||||
- **THEN** поверх ленты открыто окно карточки AURORA-9002; закрытие окна возвращает ленту с тем же периодом
|
||||
|
||||
#### Scenario: Пустой период
|
||||
- **WHEN** за выбранный период событий нет ни в Jira, ни в Вики
|
||||
- **THEN** показано «за период изменений нет»
|
||||
|
||||
#### Scenario: Вчера
|
||||
- **WHEN** выбран период «Вчера», сейчас 22.10 14:05 местного времени
|
||||
- **THEN** запросы источников с `since` = 21.10 00:00 и `until` = 22.10 00:00 местного времени
|
||||
|
||||
#### Scenario: Комментарий с разметкой
|
||||
- **WHEN** в ленте событие комментария с текстом `h3. Итог` и `*Готово*, см. [протокол|https://x.ru/p] и [~ivanov]`
|
||||
- **THEN** строка события — «комментарий: Итог Готово, см. протокол и @ivanov»
|
||||
|
||||
#### Scenario: Jira и Вики вперемешку
|
||||
- **WHEN** сегодня в 10:00 сменился статус AURORA-1, в 11:00 опубликована правка DOC-3, в 12:00 — комментарий к AURORA-2
|
||||
- **THEN** группы сверху вниз: AURORA-2, DOC-3, AURORA-1
|
||||
|
||||
#### Scenario: Проект без вики
|
||||
- **WHEN** у проекта `sfm_kedr` нет корня вики
|
||||
- **THEN** флажков источников нет, показаны события Jira; запроса `GET /wiki/changes` нет
|
||||
|
||||
#### Scenario: Ошибка одного источника
|
||||
- **WHEN** `GET /wiki/changes` ответил 500, `GET /changes` — событиями
|
||||
- **THEN** события Jira показаны; на месте правок вики — сообщение об ошибке с кнопкой «Повторить»
|
||||
|
||||
### Requirement: Оболочка и адреса
|
||||
Приложение SHALL открываться под префиксом `/app/`. Главная страница — «Обзор» ядра (`/app/?project=<метка>`,
|
||||
требование «Обзор проекта»); страница ядра «Лента» — `/app/feed` (требование «Лента изменений»). Страницы модулей SHALL
|
||||
лежать под префиксом имени модуля: модуль jira — `/app/jira/links` («Связи» — доска связей), `/app/jira/backlog`
|
||||
(«Бэклог»), `/app/jira/sprint` («Спринт»), `/app/jira/kanban` («Kanban»; три последних — требование «Спринты и
|
||||
бэклог»), `/app/jira/issues` (список задач) и `/app/jira/issues/{key}` (карточка задачи — окно поверх страницы,
|
||||
требование «Карточка задачи»); модуль wiki — `/app/wiki` (требование «Вики проекта») и
|
||||
`/app/wiki/pages/{code}/versions/{version}` (окно правки поверх страницы, требование «Правки вики в ленте»). Прежние адреса `/app/board` и `/app/jira/board` (на `/app/jira/links`),
|
||||
`/app/issues`, `/app/issues/{key}`, `/app/jira/feed` (на `/app/feed`) SHALL перенаправлять на новые с сохранением
|
||||
строки запроса, без новой записи истории. Неизвестный путь — страница «не найдено» со ссылками на «Обзор», «Ленту» и
|
||||
разделы модулей.
|
||||
|
||||
Боковая панель SHALL быть закреплена у левого края окна на всю его высоту и MUST NOT прокручиваться вместе со
|
||||
страницей; если её содержимое не помещается по высоте, прокручивается сама панель. Сверху вниз панель SHALL содержать:
|
||||
название «SDLC Hub» и кнопку «Свернуть панель»; выпадающий список проектов («<метка> — <название>», текущий отмечен, в
|
||||
конце «+ Новый проект…»; требование «Проект в приложении»); пункты «Обзор» и «Лента»; разделы модулей группами —
|
||||
заголовок группы — название модуля («Jira», «Вики»), под ним ссылки разделов модуля со значками (текущий отмечен
|
||||
`aria-current="page"`); внизу — блок состояния модулей (требование «Состояние и обновление модулей»), переключатель
|
||||
темы (требование «Тема») и меню с действием «Сменить токен». Свёрнутая панель SHALL быть узкой полосой с теми же
|
||||
действиями значками: «Развернуть панель», метка текущего проекта (полностью — в подсказке), «Обзор», «Лента», разделы
|
||||
(группы разделены линией), по значку состояния на модуль, тема, меню; названия — в подсказках и `aria-label`. Свёртка
|
||||
SHALL храниться в `localStorage` под ключом `sdlc-hub-sidebar` (`collapsed`; без записи — развёрнута).
|
||||
|
||||
На экранах уже 640 px панели в потоке нет: сверху SHALL быть закреплена строка с кнопкой «Меню», меткой текущего
|
||||
проекта и сводной точкой состояния модулей (ошибка, если она есть у какого-нибудь модуля; иначе — пакет в работе;
|
||||
иначе — все агенты на связи или нет), которая открывает панель. «Меню» открывает развёрнутую панель поверх страницы
|
||||
модальным окном: фокус переходит в панель и остаётся в ней, страница под ней недоступна; панель закрывается выбором
|
||||
раздела, клавишей Escape (если не открыто вложенное меню — тогда Escape закрывает только его) или щелчком мимо неё; фокус
|
||||
возвращается на «Меню». Горизонтальной прокрутки страницы на любой ширине нет.
|
||||
|
||||
Заголовок вкладки браузера SHALL быть «<название страницы> · <метка проекта> · SDLC Hub» (без проекта — без средней
|
||||
части); название страницы даёт оболочка («Обзор», «Лента») или модуль. Неожиданная ошибка отрисовки страницы SHALL
|
||||
показываться сообщением с кнопкой «Перезагрузить», не пустым экраном.
|
||||
|
||||
#### Scenario: Прежний адрес доски связей
|
||||
- **WHEN** открыт `/app/jira/board?project=AWPCA`
|
||||
- **THEN** адрес — `/app/jira/links?project=AWPCA`, показана доска связей, в меню отмечен пункт «Связи», заголовок
|
||||
вкладки — «Связи · AWPCA · SDLC Hub»
|
||||
|
||||
#### Scenario: Переход между разделами
|
||||
- **WHEN** на доске в боковой панели выбран раздел «Задачи» группы «Jira»
|
||||
- **THEN** адрес — `/app/jira/issues` с текущим проектом, показан список задач; «назад» возвращает на доску
|
||||
|
||||
#### Scenario: Раздел «Лента»
|
||||
- **WHEN** на доске в боковой панели выбран пункт «Лента»
|
||||
- **THEN** адрес — `/app/feed` с текущим проектом, показана лента изменений за сегодня; пункт отмечен в панели
|
||||
|
||||
#### Scenario: Прежний адрес ленты
|
||||
- **WHEN** открыт `/app/jira/feed?project=AWPCA&period=7d`
|
||||
- **THEN** адрес заменён на `/app/feed?project=AWPCA&period=7d`, показана общая лента
|
||||
|
||||
#### Scenario: Сменить токен
|
||||
- **WHEN** в меню боковой панели выбрано «Сменить токен»
|
||||
- **THEN** токен очищен, показан экран входа
|
||||
|
||||
#### Scenario: Панель на месте при прокрутке
|
||||
- **WHEN** доска прокручена до самого низа
|
||||
- **THEN** боковая панель видна целиком на прежнем месте: разделы и блок состояния модулей доступны без прокрутки вверх
|
||||
|
||||
#### Scenario: Свёрнутая панель
|
||||
- **WHEN** нажата «Свернуть панель», страница перезагружена
|
||||
- **THEN** панель свёрнута в полосу значков, доска шире; значок «Задачи» открывает список задач; видны два значка
|
||||
состояния — Jira и Вики; «Развернуть панель» возвращает полную панель
|
||||
|
||||
#### Scenario: Узкий экран
|
||||
- **WHEN** ширина окна 390 px, нажата «Меню», в панели выбран раздел «Задачи»
|
||||
- **THEN** панель открылась поверх страницы, фокус в панели; после выбора панель закрыта, фокус на «Меню», показан список
|
||||
задач; горизонтальной прокрутки нет
|
||||
|
||||
#### Scenario: Прежний адрес
|
||||
- **WHEN** открыт `/app/issues/AURORA-1536?project=AWPCA`
|
||||
- **THEN** адрес заменён на `/app/jira/issues/AURORA-1536?project=AWPCA`, карточка открыта поверх списка задач
|
||||
|
||||
#### Scenario: Заголовок вкладки
|
||||
- **WHEN** открыта страница «Вики» проекта `AWPCA`
|
||||
- **THEN** заголовок вкладки — «Вики · AWPCA · SDLC Hub»
|
||||
|
||||
### Requirement: Обзор проекта
|
||||
Страница ядра «Обзор» (`/app/?project=<метка>`, пункт «Обзор» боковой панели) SHALL показывать шапку «Обзор» с
|
||||
названием проекта и под ней карточку на каждый модуль, у которого есть данные для проекта: название модуля, агент (с
|
||||
версией из `GET /agents`), свежесть с охватом, текущее событие пакета, вторичная кнопка обновления и сводка, которую
|
||||
даёт модуль. Сводка jira: число своих задач проекта, из них открытых и просроченных (по снимку доски проекта), ссылки
|
||||
«Доска связей», «Задачи», «Лента» (общая лента с источником Jira — `/app/feed?project=<метка>&sources=jira`). Сводка
|
||||
wiki: число страниц, поле поиска по вики (Enter открывает «Вики» с этим поиском), ссылки «Вики» и «Правки» (общая
|
||||
лента с источником Вики — `/app/feed?project=<метка>&sources=wiki`). Модуль без данных для проекта SHALL показывать карточку с объяснением и действием модуля (у
|
||||
вики — «Привязать корень вики» ведёт на страницу «Вики»). Под карточками — «Последние пакеты проекта»: до 10 из
|
||||
`GET /batches?project=<метка>`: номер, модуль, название, автор, статус, время создания.
|
||||
|
||||
#### Scenario: Главная
|
||||
- **WHEN** открыт `/app/` без проекта, последний выбранный — `AWPCA`
|
||||
- **THEN** адрес `/app/?project=AWPCA`, две карточки — «Jira» и «Вики», список последних пакетов
|
||||
|
||||
#### Scenario: Поиск по вики с обзора
|
||||
- **WHEN** на «Обзоре» `AWPCA` в поле поиска вики введено «согласов» и нажат Enter
|
||||
- **THEN** открыта «Вики» с результатами поиска «согласов»
|
||||
|
||||
#### Scenario: Проект без вики
|
||||
- **WHEN** «Обзор» проекта `sfm_kedr` без корня вики
|
||||
- **THEN** карточка «Вики» с объяснением и ссылкой «Привязать корень вики»
|
||||
|
||||
### Requirement: Спринты и бэклог
|
||||
Модуль jira SHALL показывать для проекта с доской страницы по образцу Jira — только для просмотра: на них MUST NOT быть
|
||||
действий, меняющих Jira (перетаскивания, кнопок спринтов, смены статуса или ранга). Пункты меню группы «Jira» —
|
||||
«Связи», «Задачи» и по типу доски текущего проекта (`board_type` из `GET /projects`): `scrum` — «Бэклог» и
|
||||
«Спринт»; `kanban` — «Kanban»; доски нет или тип неизвестен — новых пунктов нет, а прямой переход на их адрес
|
||||
показывает «У проекта нет scrum-доски» («…kanban-доски» для «Kanban») со ссылкой на «Связи». Данные — `GET
|
||||
/agile?project=<текущий>`; если доска есть, но её данных от агента нет (`board = null` или `board.fetched_at = null`) —
|
||||
«Нужен агент v15 и обновление из Jira» с кнопкой «Обновить» блока состояния модуля; данные доски есть (`fetched_at` задан), а колонок нет (`columns = null`) — «Не удалось
|
||||
прочитать настройки доски из Jira» и та же кнопка. Пока проекты не загружены, условные пункты меню скрыты; смена проекта
|
||||
на странице, которой у нового проекта нет, оставляет адрес и показывает сообщение «У проекта нет …-доски». Даты
|
||||
спринтов (ISO-8601 Jira) показываются «ДД.ММ» в часовом поясе браузера; «осталось N дн.» — от сегодня до конца спринта.
|
||||
|
||||
Строка задачи и карточка задачи на этих страницах: ключ, значок типа, тема, исполнитель (или «—»), оценка (число или
|
||||
«—»); выполненная — приглушена. Щелчок открывает карточку задачи (окно поверх страницы, как в списке задач). Оценки и
|
||||
их суммы SHALL показываться числом не более чем с двумя знаками после запятой без хвостовых нулей (`0.1 + 0.2` —
|
||||
«0.3», `3` — «3»).
|
||||
|
||||
**Значок типа** SHALL различать по `issue_type` без учёта регистра: эпик («Эпик», «Epic», «Фича»), история
|
||||
(«История», «Story»), задача («Задача», «Task»), ошибка («Ошибка», «Bug»), подзадача («Подзадача», «Sub-task»,
|
||||
«Subtask»); прочие и пустой тип — нейтральный значок. Значок — цветной квадрат с символом цвета `--color-text-on-primary`, контрастным в обеих темах, у каждого вида свои
|
||||
цвет и символ (эпик — молния, история — закладка, задача — галочка, ошибка — круг, подзадача — два квадрата), в обеих
|
||||
темах; название типа — в `aria-label` и подсказке.
|
||||
|
||||
**Панель фильтров** — над содержимым «Бэклога», «Спринта» и «Kanban»: поиск «Ключ или тема» (подстрока без учёта
|
||||
регистра в ключе или теме), выбор «Исполнитель» («все», «Без исполнителя», исполнители задач страницы по алфавиту),
|
||||
переключатель «Скрыть выполненные» (только «Бэклог»; на «Спринте» и «Kanban» выполненное скрывают колонкой, сохранённое
|
||||
значение там не применяется); на «Бэклоге» — кнопка «Эпики» (показать или скрыть панель эпиков); на «Спринте» — выбор
|
||||
спринтов; на «Спринте» и «Kanban» — меню «Колонки». Кнопка «Сбросить фильтры» SHALL быть в панели всегда и снимать все
|
||||
фильтры (поиск, исполнитель, эпик, «Скрыть выполненные»); пока ни один не задан, она неактивна — панель не сдвигается,
|
||||
когда фильтр появляется или снимается. Фильтры действуют вместе
|
||||
(И). Сохранённое значение исполнителя или эпика, которого нет в данных, SHALL оставаться выбранным и видимым в списке.
|
||||
|
||||
**Запоминание:** свёрнутые секции, выбранный эпик, исполнитель, «Скрыть выполненные», видимость панели эпиков и скрытые
|
||||
колонки SHALL храниться в `localStorage` под ключом `sdlc-hub-agile` — JSON по метке проекта и странице (`backlog`,
|
||||
`sprint`, `kanban`); у разных страниц и проектов свои значения. Поиск и выбор спринтов не запоминаются. Нечитаемое или
|
||||
закрытое хранилище — значения по умолчанию, страница работает.
|
||||
|
||||
**«Бэклог»** (`/app/jira/backlog`), сверху вниз: секции активных спринтов, будущих спринтов, «Бэклог», затем
|
||||
свёрнутый список «Прошлые спринты (N)». Заголовок секции спринта: имя, состояние, даты «ДД.ММ–ДД.ММ», цель (если есть),
|
||||
«N задач», «S SP (выполнено D)» и «без оценки: U», если `U > 0` — итоги спринта, фильтры их не меняют; если фильтры
|
||||
скрыли часть задач секции — ещё «показано K». Задачи секции — по рангу. Секция без задач показывает «Нет задач»; все
|
||||
задачи скрыты фильтрами — «Нет задач по фильтрам». Любую секцию (спринта и «Бэклог») SHALL можно свернуть и развернуть
|
||||
щелчком по заголовку (кнопка с `aria-expanded`); по умолчанию развёрнуты все. Прошлый спринт в списке — строка «имя ·
|
||||
закрыт ДД.ММ · выполнено X из N · S SP»; раскрытие показывает его задачи с пометкой «не выполнено» у невыполненных.
|
||||
|
||||
Эпики (задачи вида «эпик») MUST NOT показываться строками в секциях «Бэклога». Панель «Эпики» — слева от секций:
|
||||
пункт «Все задачи», затем эпики — каждая задача-эпик из данных страницы по рангу, затем эпики, на которые ссылается
|
||||
`epic_key` задач, но строки которых в данных нет, по ключу; в конце — «Без эпика». Пункт эпика: тема (или ключ, если
|
||||
темы нет), ключ, число задач эпика в секциях активных и будущих спринтов и «Бэклога». Выбор пункта (кнопка с
|
||||
`aria-pressed`) фильтрует все секции, включая прошлые спринты; повторный выбор выбранного эпика возвращает «Все
|
||||
задачи». При «Скрыть выполненные» выполненные эпики в панели не показываются (кроме выбранного). На узком экране
|
||||
(контейнер страницы уже 900 px) панель SHALL становиться выбором «Эпик» в панели фильтров.
|
||||
|
||||
«Скрыть выполненные» на «Бэклоге» скрывает выполненные задачи в секциях активных и будущих спринтов и «Бэклога»;
|
||||
прошлые спринты показываются полностью.
|
||||
|
||||
**«Спринт»** (`/app/jira/sprint`): выбранные спринты доски — по умолчанию все активные. Выбор спринтов — список с
|
||||
отметками: активные, затем будущие (по дате начала, без даты — в конце); выбор хранится в адресе
|
||||
`?sprint=<id>[,<id>…]` (переход без новой записи истории); идентификаторы, которых нет среди активных и будущих,
|
||||
отбрасываются, если не осталось ни одного — все активные. Над доской — заголовок каждого выбранного спринта (имя,
|
||||
даты, «осталось N дн.» или «срок прошёл» — только у активного, цель, суммы оценок), под ними колонки доски Jira с числом
|
||||
задач; задачи доски — задачи выбранных спринтов, карточки в колонке — по рангу; колонка «Без колонки» — для статусов вне
|
||||
настроек доски, только если в ней есть задачи. Нет активного спринта и ничего не выбрано — «Нет активного спринта» и
|
||||
ссылка на «Бэклог»; если есть будущие спринты, выбор спринтов при этом доступен.
|
||||
|
||||
**«Kanban»** (`/app/jira/kanban`): колонки доски из `board_issues`, карточки по рангу, у последней колонки подпись
|
||||
«за 14 дней»; если `kanban_backlog = true` — над колонками свёрнутый список «Бэклог (N)» по рангу; поиск и исполнитель
|
||||
действуют и на него.
|
||||
|
||||
**Колонки доски** («Спринт» и «Kanban»): заголовок колонки — `h3`. Меню «Колонки» — отметки по колонкам доски (и «Без
|
||||
колонки», если она есть); снятая отметка скрывает колонку, последнюю видимую скрыть нельзя (отметка недоступна); если
|
||||
скрыты колонки — на кнопке их число. Доска SHALL занимать высоту окна под заголовком и панелью фильтров (не меньше
|
||||
360 px; на окне уже 640 px — не меньше 240 px), все колонки — во всю высоту доски; карточки, не помещающиеся по высоте, прокручиваются внутри своей колонки.
|
||||
Колонки, не помещающиеся по ширине, SHALL прокручиваться по горизонтали внутри доски, не страницы, и полоса горизонтальной
|
||||
прокрутки доски SHALL находиться в пределах окна при любом числе карточек. Заголовок вкладки — «Бэклог», «Спринт»,
|
||||
«Kanban» по общему правилу оболочки.
|
||||
|
||||
#### Scenario: Меню scrum-проекта
|
||||
- **WHEN** текущий проект `AWPCA`, `board_type = scrum`
|
||||
- **THEN** в группе «Jira» пункты «Связи», «Бэклог», «Спринт», «Задачи»; пункта «Kanban» нет
|
||||
|
||||
#### Scenario: Проект без доски
|
||||
- **WHEN** текущий проект `STOCKS_DRRK` без доски, открыт `/app/jira/backlog?project=STOCKS_DRRK`
|
||||
- **THEN** пунктов «Бэклог», «Спринт», «Kanban» в меню нет; страница — «У проекта нет scrum-доски» со ссылкой на «Связи»
|
||||
|
||||
#### Scenario: Бэклог только смотрит
|
||||
- **WHEN** открыт «Бэклог» `AWPCA`
|
||||
- **THEN** на странице нет перетаскиваемых элементов; кнопки и поля — только свёртка секций, панель фильтров, пункты
|
||||
панели эпиков и открытие карточки задачи; ни одно из них не ставит команд
|
||||
|
||||
#### Scenario: Прошлый спринт
|
||||
- **WHEN** спринт `AWPCA_Sprint_2_VGO` закрыт, в нём 73 задачи, 61 в последней колонке, оценки у 10 задач на 25 SP
|
||||
- **THEN** в «Прошлые спринты» строка «AWPCA_Sprint_2_VGO · закрыт ДД.ММ · выполнено 61 из 73 · 25 SP»; раскрытие —
|
||||
73 задачи, 12 с пометкой «не выполнено»
|
||||
|
||||
#### Scenario: Агент без досок
|
||||
- **WHEN** `AWPCA` scrum, агент v14 (данных доски нет), открыт «Спринт»
|
||||
- **THEN** «Нужен агент v15 и обновление из Jira» и кнопка «Обновить»
|
||||
|
||||
#### Scenario: Свёрнутый активный спринт
|
||||
- **WHEN** на «Бэклоге» свёрнута секция активного спринта, страница перезагружена
|
||||
- **THEN** секция свёрнута (видны заголовок и итоги, задач нет), остальные развёрнуты
|
||||
|
||||
#### Scenario: Фильтр по эпику
|
||||
- **WHEN** в бэклоге 3 задачи эпика `AURORA-10` и 5 других, в активном спринте 2 задачи `AURORA-10`; в панели «Эпики»
|
||||
выбран `AURORA-10`
|
||||
- **THEN** в секции спринта 2 задачи и «показано 2», в «Бэклоге» 3; строки самого `AURORA-10` в секциях нет; у пункта
|
||||
`AURORA-10` в панели — 5
|
||||
|
||||
#### Scenario: Скрыть выполненные
|
||||
- **WHEN** в активном спринте 10 задач, 4 выполнены; включено «Скрыть выполненные»
|
||||
- **THEN** в секции 6 задач, заголовок — «10 задач» и «показано 6»; «Прошлые спринты» показывают все свои задачи
|
||||
|
||||
#### Scenario: Исполнитель на доске спринта
|
||||
- **WHEN** на «Спринте» выбран исполнитель `ivanov`
|
||||
- **THEN** на доске только его карточки; числа в заголовках колонок — по видимым карточкам; на «Бэклоге» исполнитель не
|
||||
выбран (свой фильтр у каждой страницы)
|
||||
|
||||
#### Scenario: Будущий спринт на доске
|
||||
- **WHEN** у доски активный спринт 101 и будущий 102; в выборе спринтов отмечен и 102
|
||||
- **THEN** адрес — `?sprint=101,102`; над доской два заголовка, «осталось N дн.» только у 101; на доске задачи обоих
|
||||
|
||||
#### Scenario: Сброс фильтров на месте
|
||||
- **WHEN** открыт «Спринт» без фильтров, затем выбран исполнитель
|
||||
- **THEN** «Сбросить фильтры» видна и неактивна до выбора, после — активна на том же месте; флажка «Скрыть выполненные»
|
||||
на «Спринте» нет
|
||||
|
||||
#### Scenario: Скрытая колонка
|
||||
- **WHEN** на «Спринте» в меню «Колонки» снята отметка «Выполнено», страница перезагружена
|
||||
- **THEN** колонки «Выполнено» нет, на кнопке «Колонки» — 1; остальные колонки на месте
|
||||
|
||||
#### Scenario: Широкая доска
|
||||
- **WHEN** окно 2560 × 1300 px, у доски 12 колонок, в одной из них 40 карточек
|
||||
- **THEN** все колонки одной высоты и помещаются в окно по высоте; полоса горизонтальной прокрутки доски видна в окне;
|
||||
прокрутка доски вправо до конца показывает «Без колонки» целиком; страница горизонтально не прокручивается
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Правки вики в ленте
|
||||
Модуль wiki SHALL давать ленте источник «Вики»: правки страниц проекта за период (`GET /wiki/changes` с `project`,
|
||||
`since`, `until`). Правки сгруппированы по странице; у группы — название страницы ссылкой на неё в разделе «Вики»
|
||||
(`/app/wiki?project=<метка>&page=<код>`) и путь в дереве; под ней правки по времени, новые сверху, каждая — время
|
||||
(`ЧЧ:ММ`, с датой `ДД.ММ`, если правка не в текущих сутках), автор (имя) и «+A / −R». `history_at = null` — на месте
|
||||
правок вики сообщение «история вики ещё не загружена — нужен eva-agent v2».
|
||||
|
||||
Нажатие на правку SHALL открывать окно поверх ленты по адресу `/app/wiki/pages/{code}/versions/{version}?project=<метка>`
|
||||
(фоновая страница остаётся под ним, закрытие — кнопкой, Escape или «назад» — возвращает ленту с тем же периодом и
|
||||
источниками). Адрес окна, открытый напрямую (перезагрузка, ссылка), показывает окно поверх страницы «Вики» с этой
|
||||
страницей (`/app/wiki?project=<метка>&page=<код>`); закрытие оставляет её. Окно показывает: название и путь страницы, «правка N из M»,
|
||||
автора, время, «+A / −R» и дифф правки (`GET /wiki/pages/{code}/diff` с `version`): строки, добавленные и удалённые, —
|
||||
цветами состояния успеха и ошибки из токенов темы с подписью для экранных читалок, строки `@@` — второстепенным цветом,
|
||||
моноширинным шрифтом, без горизонтальной прокрутки страницы (длинная строка переносится или прокручивается внутри
|
||||
окна). Дифф длиннее одной части — кнопка «Показать дальше» догружает следующую (`offset = next_offset`). Кнопки
|
||||
«Предыдущая правка» и «Следующая правка» переключают версию той же страницы (по `GET /wiki/pages/{code}/versions`),
|
||||
заменяя адрес окна без новой записи истории, — «назад» по-прежнему закрывает окно; у первой и последней
|
||||
соответствующая кнопка недоступна. Ссылка «Открыть страницу» ведёт на страницу в разделе «Вики».
|
||||
Окно только показывает: в Eva и в хабе ничего не меняет.
|
||||
|
||||
Контракт веб-модуля SHALL получить необязательное поле источника ленты: ядро передаёт источнику проект, границы
|
||||
периода и признак «выбран», а источник отдаёт свои группы со временем последнего события, состояние (загрузка,
|
||||
ошибка, `truncated`, пустая история) и, при необходимости, свои настройки над лентой (виды событий Jira). Ядро MUST NOT
|
||||
знать видов событий Jira и полей правок вики (требование «Граница модулей в вебе»).
|
||||
|
||||
#### Scenario: Правка из ленты
|
||||
- **WHEN** в ленте AWPCA за сегодня группа DOC-101252 «17.2.2.1 ЭФ «Ознакомление перед подписанием»» с правкой «12:40
|
||||
Дмитрий Соснов +40 / −12»; нажата правка
|
||||
- **THEN** поверх ленты окно: «правка 4 из 4», Соснов, 12:40, «+40 / −12», дифф с подсвеченными строками; запрос
|
||||
`GET /wiki/pages/DOC-101252/diff?project=AWPCA&version=DOCH-…`
|
||||
|
||||
#### Scenario: Предыдущая правка
|
||||
- **WHEN** в окне правки 4 из 4 нажата «Предыдущая правка»
|
||||
- **THEN** показана правка 3 из 4 той же страницы; «Следующая правка» доступна; «назад» браузера закрывает окно и
|
||||
возвращает ленту
|
||||
|
||||
#### Scenario: Окно по ссылке
|
||||
- **WHEN** открыт `/app/wiki/pages/DOC-101252/versions/DOCH-3159076?project=AWPCA` в новой вкладке
|
||||
- **THEN** окно правки поверх страницы «Вики» с DOC-101252; закрытие окна — адрес `/app/wiki?project=AWPCA&page=DOC-101252`
|
||||
|
||||
#### Scenario: Длинный дифф
|
||||
- **WHEN** у правки `next_offset = 20000`
|
||||
- **THEN** под диффом кнопка «Показать дальше»; нажатие догружает часть с `offset=20000` ниже уже показанной
|
||||
|
||||
#### Scenario: История не загружена
|
||||
- **WHEN** у проекта `history_at = null`
|
||||
- **THEN** на месте правок вики — «история вики ещё не загружена — нужен eva-agent v2»
|
||||
|
||||
#### Scenario: Окно на узком экране
|
||||
- **WHEN** ширина окна 390 px, открыто окно правки с длинными строками Markdown
|
||||
- **THEN** горизонтальной прокрутки страницы нет; строки диффа переносятся или прокручиваются внутри окна
|
||||
102
openspec/changes/wiki-history/specs/wiki/spec.md
Normal file
102
openspec/changes/wiki-history/specs/wiki/spec.md
Normal file
@ -0,0 +1,102 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: История правок страниц
|
||||
Хаб SHALL хранить опубликованные версии страниц дерева проекта: код версии Eva (`DOCH-…`), код страницы, время правки
|
||||
(UTC), автора (`login`, `name`), Markdown версии и число строк, добавленных и удалённых против предыдущей версии той же
|
||||
страницы (порядок версий — по времени, затем по коду; у первой — против пустого текста). Время правки — время создания
|
||||
версии в Eva (`cmf_created_at`). Версии страниц, которых нет в дереве проекта, хаб MUST NOT хранить; неопубликованные
|
||||
версии (черновики) агент MUST NOT присылать.
|
||||
|
||||
При выдаче `wiki_sync` хаб SHALL дописывать в параметры команды `known_versions` — объект `{<код страницы>: [<код
|
||||
версии>…]}` по версиям, уже хранящимся у страниц текущей копии проекта (без копии — `{}`); в `command.params` в базе
|
||||
поле MUST NOT записываться. Агент `wiki` SHALL после страниц прислать опубликованные версии страниц дерева, которых нет
|
||||
в `known_versions` их страницы, пачками `POST /agent/wiki/versions {command_id, versions}`: версия — `{page_code, code,
|
||||
created_at, author: {login, name}, html, attachments, links}` (`created_at` — ISO-8601 со смещением; `login` и `name`
|
||||
— строки или `null`, если Eva автора не отдала; `attachments` и `links` — как у страниц, по `html` версии), в пачке не
|
||||
больше 50 версий, — и отчитаться `done` с `result.versions` —
|
||||
числом присланных версий. Пачка по команде не в `claimed` или не `wiki_sync` — 409; неверная форма версии — 422
|
||||
`bad_versions` с кодом версии и полем. Хаб SHALL переводить HTML версии в Markdown при приёме вне транзакции записи
|
||||
(ссылки на вложения — текстом `📎 <имя>` без ссылки, ссылки на страницы — `page:<код>`) и складывать в стадию команды.
|
||||
|
||||
По отчёту `done` с `result.versions` хаб SHALL в той же операции, что и копию (см. «Синхронизация вики»): добавить
|
||||
версии из стадии к страницам, чьи коды есть в `codes`, отбросив остальные; удалить версии страниц, которых нет в
|
||||
`codes`; посчитать «+/−» у новых версий и у версии, следующей за вставленной раньше неё; записать время применения
|
||||
истории `history_at`. Присланных версий меньше `result.versions` — команда `failed` «нет версий: пришло N из M», копия и
|
||||
история прежние. Отчёт `done` без `result.versions` (агент без истории) SHALL обновлять копию как прежде и не менять
|
||||
историю и `history_at`. Отчёт `failed` и отброшенный отчёт по сменённому корню историю не меняют. Сброс корня SHALL
|
||||
удалять историю проекта вместе с копией. Стадия версий очищается так же, как стадия страниц.
|
||||
|
||||
#### Scenario: Первая загрузка истории
|
||||
- **WHEN** у `AWPCA` дерево DOC-1, DOC-2; в хабе версий нет; выдан `wiki_sync`
|
||||
- **THEN** в параметрах выдачи `known_versions = {}`; агент прислал 3 версии DOC-1 и 2 версии DOC-2, отчёт `done
|
||||
{versions: 5}`; в хабе 5 версий, у первой версии каждой страницы «+N / −0», `history_at` заполнен
|
||||
|
||||
#### Scenario: Догрузка новых версий
|
||||
- **WHEN** у DOC-1 в хабе версии DOCH-10, DOCH-11; в Eva опубликована DOCH-12
|
||||
- **THEN** при выдаче `known_versions = {"DOC-1": ["DOCH-10", "DOCH-11"], …}`; агент прислал только DOCH-12; у неё
|
||||
«+/−» против DOCH-11
|
||||
|
||||
#### Scenario: Агент без истории
|
||||
- **WHEN** агент v1 отчитался `done {root_title, codes, eva_calls}` без `versions`
|
||||
- **THEN** копия обновлена, версии и `history_at` прежние
|
||||
|
||||
#### Scenario: Страница ушла из дерева
|
||||
- **WHEN** у DOC-3 в хабе 4 версии; новая синхронизация вернула `codes` без DOC-3
|
||||
- **THEN** версий DOC-3 в хабе нет
|
||||
|
||||
#### Scenario: Версия чужой страницы
|
||||
- **WHEN** в стадии версия с `page_code = DOC-99`, которой нет в `codes`
|
||||
- **THEN** версия не записана, команда `done`
|
||||
|
||||
#### Scenario: Потерянная пачка версий
|
||||
- **WHEN** отчёт `done {versions: 5}`, в стадии 3 версии
|
||||
- **THEN** команда `failed` «нет версий: пришло 3 из 5», копия и история прежние
|
||||
|
||||
### Requirement: Маршруты истории вики
|
||||
Хаб SHALL предоставлять под клиентским токеном (проект — `project=<метка>`, неизвестный — 404, без корня — 409
|
||||
`no_root`), читая только базу хаба:
|
||||
- `GET /wiki/changes?project=&since=&until=&author=&limit=` — правки страниц текущей копии проекта со временем в
|
||||
`[since, until]`. `since`, `until` — ISO-8601 со смещением или `Z` (без смещения — 422); без `until` — текущее время;
|
||||
без `since` — `until` минус 24 часа; `author` — подстрока имени или логина без учёта регистра; `limit` — по умолчанию
|
||||
500, 1–2000. Ответ — `{since, until, synced_at, history_at, truncated, changes: [{at, page_code, title, path,
|
||||
version, author: {login, name}, added, removed}]}` по `at` по убыванию, затем по `version` по убыванию; правок больше
|
||||
`limit` — первые `limit` и `truncated = true`. Неверное время, `since` позже `until`, `limit` вне 1..2000 — 422
|
||||
`bad_request` с именем параметра. `history_at = null` — история проекта ещё не загружалась.
|
||||
- `GET /wiki/pages/{code}/versions?project=` — `{code, title, versions: [{version, at, author: {login, name}, added,
|
||||
removed}]}`, новые первыми; страницы нет в копии — 404.
|
||||
- `GET /wiki/pages/{code}/diff?project=&version=&offset=` — правка `version` (без параметра — последняя) против
|
||||
предыдущей версии страницы: `{code, title, path, version, prev_version, at, author: {login, name}, added, removed,
|
||||
index, total, offset, next_offset, diff}`, где `diff` — unified diff Markdown (контекст 3 строки; у первой версии —
|
||||
против пустого, `prev_version = null`) с символа `offset` до 20 000 символов, `next_offset` — начало следующей части
|
||||
или `null`, `index` — номер версии от старой (с 1), `total` — число версий. Страницы нет в копии — 404; версии нет у
|
||||
страницы — 404 `no_version`; у страницы нет версий — 404 `no_versions`.
|
||||
|
||||
`GET /wiki/pages/{code}` SHALL дополнительно отдавать `modified_at`, `modified_by: {login, name}` (последняя версия;
|
||||
нет версий — `null`) и `versions` — число версий. `GET /wiki/status` SHALL дополнительно отдавать `history_at`.
|
||||
|
||||
#### Scenario: Что сделали за сегодня
|
||||
- **WHEN** у `AWPCA` 27.10 в 17:59 и 18:02 (MSK) Соснов опубликовал версии DOC-101252, 26.10 Молчанов — версию DOC-2;
|
||||
`GET /wiki/changes?project=AWPCA&since=2026-10-27T00:00:00+03:00`
|
||||
- **THEN** две правки DOC-101252, новая первой, у каждой автор `{login: "sosnov-dvi@rshbeco.ru", name: "Дмитрий
|
||||
Соснов"}`, `added`/`removed`, путь страницы в дереве; правки DOC-2 нет
|
||||
|
||||
#### Scenario: Фильтр по автору
|
||||
- **WHEN** `GET /wiki/changes?project=AWPCA&since=…&author=соснов`
|
||||
- **THEN** только правки Соснова
|
||||
|
||||
#### Scenario: Дифф последней правки
|
||||
- **WHEN** у DOC-101252 4 версии; `GET /wiki/pages/DOC-101252/diff?project=AWPCA`
|
||||
- **THEN** `index = 4`, `total = 4`, `prev_version` — третья версия, `diff` начинается с `--- ` и `+++ `
|
||||
|
||||
#### Scenario: Длинный дифф
|
||||
- **WHEN** дифф правки 45 000 символов, запрос без `offset`
|
||||
- **THEN** первые 20 000 символов, `next_offset = 20000`; запрос с `offset=40000` — остаток, `next_offset = null`
|
||||
|
||||
#### Scenario: История ещё не загружена
|
||||
- **WHEN** у `AWPCA` копия есть, агент v1, `GET /wiki/changes?project=AWPCA`
|
||||
- **THEN** 200, `history_at = null`, `changes = []`
|
||||
|
||||
#### Scenario: Последняя правка в странице
|
||||
- **WHEN** в хабе 4 версии DOC-101252, последняя — Соснов, `created_at = 2026-10-27T23:48:02+03:00`;
|
||||
`GET /wiki/pages/DOC-101252?project=AWPCA`
|
||||
- **THEN** `modified_at = "2026-10-27T20:48:02+00:00"`, `modified_by.name = "Дмитрий Соснов"`, `versions = 4`
|
||||
Loading…
x
Reference in New Issue
Block a user