Merge pull request 'Архив OpenSpec: wiki-package-d (#135)' (#137) from chore/135-archive-wiki-package-d into main

This commit is contained in:
aep 2026-10-27 18:03:08 +03:00
commit 0f9844123f
9 changed files with 81 additions and 4 deletions

View File

@ -275,6 +275,10 @@ HTTP API хаба клиентским токеном из настроек; с
синхронизаций — «вики проекта ещё не синхронизирована: sync». Набор инструментов и их описания у сервера `sdlc-jira`
(модуль jira) SHALL остаться прежним.
Значения из Eva, которые инструменты выводят внутри строки ответа (названия страниц и корня, путь, имя вложения,
фрагмент поиска, кандидаты `page` по названию), SHALL выводиться одной строкой: переводы строк (`\r`, `\n`) и пробелы
вокруг них заменяются одним пробелом. Тело страницы (Markdown после шапки `page`) выводится как есть.
#### Scenario: Поиск из сессии awp-ca
- **WHEN** сервер запущен с `--project AWPCA`, копия синхронизирована, вызван `search {query: "статус согласован"}`
- **THEN** ответ — страницы с кодами и фрагментами; запрос в хаб — `GET /wiki/search?project=AWPCA&q=…`
@ -292,6 +296,10 @@ HTTP API хаба клиентским токеном из настроек; с
- **WHEN** запущен `sdlc-hub mcp jira --project AWPCA`
- **THEN** имя сервера `sdlc-jira`, список инструментов и описания совпадают с эталоном до change
#### Scenario: Перевод строки в названии страницы
- **WHEN** у DOC-3 название «Статусы\nпуть: поддельный», вызван `page {code: DOC-3}`
- **THEN** первая строка ответа — «DOC-3 — Статусы путь: поддельный», строка «путь:» в шапке одна — настоящая
### Requirement: Спринты и бэклог в MCP
MCP `sdlc-jira` SHALL давать инструменты чтения по `GET /agile` проекта сессии (сессия без проекта — тот же отказ, что у
прочих инструментов с проектом):

View File

@ -743,6 +743,13 @@ Semibold 14/20, заголовок страницы Bold 20/28. Страницы
Markdown с таблицами, без сырого HTML; без `page` в адресе — корневая страница; ссылки `page:<код>` открывают страницу
вики (код в адресе `&page=<код>`), ссылки `attachment:<id>` скачивают вложение (запрос, опрос состояния, сохранение
файла браузером), состояние `failed` — текст ошибки рядом со ссылкой;
- вложение-картинку (`image = true` в `attachments` ответа страницы) — в тексте
страницы на месте ссылки: вложение в `ready` при открытии страницы показывается картинкой сразу (содержимое `GET
/wiki/attachments/{id}/content`, без нового скачивания агентом); нажатие на ссылку нескачанной картинки ставит
скачивание (запрос, опрос состояния) и по готовности показывает картинку вместо сохранения файла. Под картинкой
остаётся ссылка «📎 <имя>», сохраняющая файл. Картинка не шире колонки страницы. Содержимое не получено (409
`not_ready`, ошибка сети) — вместо картинки прежняя ссылка, нажатие на неё ставит скачивание и по готовности
показывает картинку; ошибка скачивания — текст рядом со ссылкой. Без нажатия приложение не ставит скачивание;
- на экранах уже 640 px выбранная страница — над деревом;
- поиск над деревом (`GET /wiki/search`): список результатов с фрагментами вместо дерева, пока строка поиска не пуста;
после очистки поиска раскрытие дерева прежнее;
@ -768,6 +775,19 @@ Semibold 14/20, заголовок страницы Bold 20/28. Страницы
- **WHEN** открыта `/app/wiki?project=AWPCA` без `page`
- **THEN** справа корневая страница проекта, в дереве она выделена
#### Scenario: Скачать картинку
- **WHEN** на странице DOC-3 вложение 7 `scheme.png` (`image = true`) в `none`; нажата ссылка «📎 scheme.png»; агент скачал файл
- **THEN** `POST /wiki/attachments/7/fetch`, опрос до `ready`, `GET /wiki/attachments/7/content`; на месте ссылки —
картинка, под ней ссылка «📎 scheme.png»; файл браузером не сохраняется
#### Scenario: Скачанная картинка видна сразу
- **WHEN** вложение 7 `scheme.png` в `ready`, открыта страница DOC-3
- **THEN** картинка показана без нажатия; `POST /wiki/attachments/7/fetch` не вызывался
#### Scenario: Не картинка
- **WHEN** вложение 8 `model.xlsx` (`image = false`) в `ready`, нажата ссылка «📎 model.xlsx»
- **THEN** файл сохраняется браузером, картинки нет
### Requirement: Состояние и обновление модулей
Каждый веб-модуль SHALL описывать своё состояние данными, а рисовать их SHALL ядро. Описание модуля: название для
показа («Jira», «Вики»); агент (имя модуля в `GET /agents`) или его отсутствие; свежесть данных для текущего проекта —

View File

@ -30,8 +30,11 @@ SHALL удалять копию проекта, его страницы, инд
label`. Её SHALL ставить: `POST /wiki/sync {project}`, задание или смена корня, фоновая работа хаба — по каждому
проекту с корнем, если с последней успешной или неудачной синхронизации прошло больше `wiki_auto_sync_hours`
(настройка модуля, по умолчанию 24; `0` — выключено). Пока у проекта есть пакет `wiki_sync` не в терминальном статусе,
новый не ставится: `POST /wiki/sync` отвечает 409 `sync_in_progress` с `batch_id` текущего. Смена или сброс корня
SHALL отменять невыданные (`queued`) пакеты `wiki_sync` проекта и при новом корне ставить новый; отчёт `done` по
новый не ставится: `POST /wiki/sync` отвечает 409 `sync_in_progress` с `batch_id` текущего. Любая другая постановка
пакета с `wiki_sync` (`POST /batches`, повтор команды `POST /commands/{id}/retry`) SHALL отклоняться с 422, если у
проекта уже есть команда `wiki_sync` с тем же `root` в `queued` или `claimed`; текст ошибки называет пакет идущей
синхронизации. Команда с другим `root` не мешает: смена корня ставит синхронизацию нового корня, пока старая ещё
выдана агенту. Смена или сброс корня SHALL отменять невыданные (`queued`) пакеты `wiki_sync` проекта и при новом корне ставить новый; отчёт `done` по
команде, чей `root` не равен текущему корню проекта, SHALL отбрасываться без изменения копии и данных
(`result.discarded = "корень сменился"`).
@ -80,6 +83,15 @@ links: [{ref, code}]}`, где `attachments` — ссылки и картинк
- **WHEN** пакет `wiki_sync` проекта `AWPCA` выполняется, вызван `POST /wiki/sync {project: AWPCA}`
- **THEN** 409 `sync_in_progress` с `batch_id` текущего пакета, нового пакета нет
#### Scenario: Повтор упавшей синхронизации во время идущей
- **WHEN** команда `wiki_sync {label: AWPCA, root: DOC-1}` 40 в `failed`, фоновая работа поставила пакет 41 с
`wiki_sync {root: DOC-1}` (`queued`); `POST /commands/40/retry`
- **THEN** 422, в тексте — пакет 41; нового пакета нет
#### Scenario: Смена корня при выданной синхронизации
- **WHEN** `wiki_sync {root: DOC-12}` выдан агенту (`claimed`); `PATCH /projects/AWPCA {wiki_root: "DOC-21"}`
- **THEN** 200, поставлен `wiki_sync {root: DOC-21}`
### Requirement: Markdown-копия вики
Копия проекта SHALL лежать в `<SDLC_HUB_HOME>/wiki/<метка>/` (каталоги 0700): у каждой страницы — каталог
`NN_<название>` внутри каталога родителя (`NN` — двузначная позиция среди соседей по `orderno`, затем по коду; у корня
@ -94,6 +106,15 @@ HTML страницы SHALL переводиться в Markdown (заголов
`[📎 <имя>](attachment:<id>)`, где `id` — строка вложения; ссылка на документ Eva с кодом `DOC-<цифры>` (в Eva — относительный
`project/Document/DOC-…`) — на `[<текст>](page:<код>)`; прочие ссылки остаются. Сырой HTML в копию не попадает.
Перевод SHALL давать корректный Markdown на любом HTML Eva:
- картинка внутри ссылки на то же вложение — на любой глубине вложенности (`<a><span><img></span></a>`) — даёт одну
ссылку на вложение;
- вложение внутри ссылки на документ Eva выводится отдельной ссылкой на вложение перед ссылкой на страницу; текст
ссылки на страницу — её текст без вложения, пустой — код страницы; ссылка внутри ссылки не появляется;
- при нескольких записях `attachments` или `links` с одним `ref` действует первая;
- пробел и круглые скобки в адресе внешней ссылки или картинки кодируются (`%20`, `%28`, `%29`);
- текст ссылки не может экранировать её закрывающую скобку: хвостовая `\` экранируется.
#### Scenario: Раскладка
- **WHEN** корень DOC-1 «АРМ КА», дети DOC-3 «Статусы» (`orderno` 1) и DOC-2 «Роли/права» (`orderno` 2)
- **THEN** `wiki/AWPCA/АРМ КА/index.md`, `…/01_Статусы/index.md`, `…/02_Роли_права/index.md`; `_tree.json` в порядке
@ -108,14 +129,31 @@ HTML страницы SHALL переводиться в Markdown (заголов
ссылка на `…/<uuid>/model.xlsx`
- **THEN** одно вложение `model.xlsx` с адресом файла; в тексте одна ссылка `[📎 model.xlsx](attachment:<id>)`, значка нет
#### Scenario: Миниатюра в обёртке
- **WHEN** `<a href="…/model.xlsx"><span><img src="…/model.xlsx.meta/thumbnail.jpg"></span></a> текст`
- **THEN** в тексте `[📎 model.xlsx](attachment:<id>) текст`
#### Scenario: Ссылка на другую страницу
- **WHEN** в HTML `<a href="project/Document/DOC-019834">Модель данных</a>`
- **THEN** в `index.md` — `[Модель данных](page:DOC-019834)`
#### Scenario: Картинка-ссылка на другую страницу
- **WHEN** `<a href="project/Document/DOC-1"><img src="…/scheme.png"></a>`, `scheme.png` — вложение
- **THEN** в тексте `[📎 scheme.png](attachment:<id>) [DOC-1](page:DOC-1)`
#### Scenario: Пробелы и скобки во внешнем адресе
- **WHEN** `<a href="http://ex.com/a b(c).html">док</a>`
- **THEN** в тексте `[док](http://ex.com/a%20b%28c%29.html)`
#### Scenario: Обратная косая черта в конце текста
- **WHEN** `<a href="project/Document/DOC-1">путь C:\</a>`
- **THEN** ссылка на `page:DOC-1` с текстом «путь C:\» остаётся ссылкой при разборе Markdown
### Requirement: Вложения вики по запросу
`POST /wiki/attachments/{id}/fetch` SHALL ставить пакет из одной команды чтения `wiki_fetch {attachment_id, url}` и
переводить вложение в `fetching`; для `fetching` — ответ текущим состоянием без нового пакета, для `ready` — сразу
`ready`. Агент `wiki` SHALL скачать файл с токеном Eva, отказаться от файла больше предела хранилища ядра (10 МБ) с
`ready`. Вложение в `ready`, чьего файла нет в `files/`, SHALL считаться нескачанным: запрос ставит скачивание заново.
Агент `wiki` SHALL скачать файл с токеном Eva, отказаться от файла больше предела хранилища ядра (10 МБ) с
ошибкой «файл больше 10 МБ — откройте в Eva», загрузить файл `POST /agent/files` и отчитаться `done {file, size}`. Хаб
SHALL проверить, что файл есть в `files/` и размер совпадает, и перевести вложение в `ready`; иначе команда и вложение
`failed`. Адрес не на хосте Eva из конфига агента агент SHALL отвергать без запроса (`failed` «чужой хост»).
@ -135,13 +173,19 @@ SHALL проверить, что файл есть в `files/` и размер
- **WHEN** вложение 7 `ready`, синхронизация вернула ту же страницу с той же ссылкой
- **THEN** у вложения тот же `id` 7 и `state = ready`, файл в `files/` не убран
#### Scenario: Файл вложения пропал
- **WHEN** вложение 7 `ready`, его файла нет в `files/`; `POST /wiki/attachments/7/fetch`
- **THEN** 202, вложение 7 `fetching`, поставлен пакет `wiki_fetch {attachment_id: 7}`
### Requirement: Маршруты вики
Хаб SHALL предоставлять под клиентским токеном (проект — `project=<метка>`, неизвестный — 404, без корня — 409
`no_root`):
- `GET /wiki/status?project=` — `{root, root_title, synced_at, pages, error, batch: {id, status} | null}`;
- `GET /wiki/tree?project=` — `{"pages": [{code, title, depth, parent_code, size}]}` в порядке обхода;
- `GET /wiki/pages/{code}?project=` — `{code, title, path, url, synced_at, markdown, attachments: [{id, name, state,
size, error}]}`; страницы нет в копии — 404;
size, error, image}]}`; `image` — `true`, если имя файла в адресе вложения (последний сегмент пути без запроса,
раскодированный) оканчивается на `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.bmp` без учёта регистра, иначе `false`
(имя вложения из Eva для этого не годится: у картинок это часто `alt` без расширения); страницы нет в копии — 404;
- `GET /wiki/search?project=&q=&limit=` (`limit` 1–50, по умолчанию 10) — `{"results": [{code, title, path, snippet}]}` по
убыванию релевантности; слова запроса ищутся по префиксу, пустой запрос — 422;
- `POST /wiki/sync {project, author?}` → 201 `{batch_id}` (409 — см. «Синхронизация вики»);
@ -157,3 +201,8 @@ SHALL проверить, что файл есть в `files/` и размер
#### Scenario: Проект без корня
- **WHEN** у `sfm_kedr` нет корня, `GET /wiki/tree?project=sfm_kedr`
- **THEN** 409 `no_root`
#### Scenario: Признак картинки
- **WHEN** у вложения 9 страницы DOC-3 имя «Схема расчёта суммы элиминации по тройке», адрес
`…/vgo-matching-1.png?version=1`; у вложения 8 имя `attachment`, адрес `…/model.xlsx`
- **THEN** в `GET /wiki/pages/DOC-3?project=AWPCA` у 9 `image = true`, у 8 `image = false`